From 9914812b9eb26ee016db37691f1d1219ac25edd8 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 27 Mar 2025 19:42:04 +0100 Subject: [PATCH 01/70] feat: logic for lending pool creation + tests --- pallets/admin-utils/src/tests/mock.rs | 4 + pallets/subtensor/src/lending_pools/mod.rs | 128 +++++++++++ pallets/subtensor/src/lib.rs | 45 +++- pallets/subtensor/src/macros/config.rs | 13 ++ pallets/subtensor/src/macros/dispatches.rs | 17 ++ pallets/subtensor/src/macros/errors.rs | 14 ++ pallets/subtensor/src/tests/lending_pools.rs | 221 +++++++++++++++++++ pallets/subtensor/src/tests/mock.rs | 8 + pallets/subtensor/src/tests/mod.rs | 1 + runtime/src/lib.rs | 8 + 10 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/lending_pools/mod.rs create mode 100644 pallets/subtensor/src/tests/lending_pools.rs diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index fc0d016198..096e8d63c0 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -199,6 +199,10 @@ impl pallet_subtensor::Config for Test { type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; + type LendingPoolsLimit = (); + type LendingPoolMinInitialDeposit = (); + type LendingPoolMaxLendingCap = (); + type LendingPoolMinEmissionsShare = (); } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/pallets/subtensor/src/lending_pools/mod.rs b/pallets/subtensor/src/lending_pools/mod.rs new file mode 100644 index 0000000000..351e546de3 --- /dev/null +++ b/pallets/subtensor/src/lending_pools/mod.rs @@ -0,0 +1,128 @@ +use super::*; +use frame_support::traits::{fungible::Mutate, tokens::Preservation}; +use sp_io::hashing::blake2_256; +use sp_runtime::traits::TrailingZeroInput; + +impl Pallet { + pub fn do_create_subnet_lending_pool( + origin: T::RuntimeOrigin, + initial_deposit: u64, + max_lending_cap: u64, + emissions_share: u64, + ) -> dispatch::DispatchResult { + let creator_coldkey = ensure_signed(origin)?; + + // Ensure we have reached the maximum number of lending pools + ensure!( + NextLendingPoolId::::get() < T::LendingPoolsLimit::get(), + Error::::LendingPoolsLimitReached + ); + // Ensure the initial deposit is above the minimum required to create a lending pool. + ensure!( + initial_deposit >= T::LendingPoolMinInitialDeposit::get(), + Error::::LendingPoolInitialDepositTooLow + ); + // Ensure the max lending cap is at least superior to the initial deposit. + ensure!( + max_lending_cap > initial_deposit, + Error::::LendingPoolLendingCapInferiorToInitialDeposit + ); + // Ensure the max lending cap is not greater than the maximum allowed. + ensure!( + max_lending_cap <= T::LendingPoolMaxLendingCap::get(), + Error::::LendingPoolLendingCapTooHigh + ); + // Ensure the emisions share is at a minimum of some value. + ensure!( + emissions_share >= T::LendingPoolMinEmissionsShare::get(), + Error::::LendingPoolEmissionsShareTooLow + ); + // Ensure the emissions share is not greater than 100%. + ensure!( + emissions_share <= 100, + Error::::LendingPoolEmissionsShareTooHigh + ); + // Ensure creator coldkey contains the initial deposit. + ensure!( + Self::get_coldkey_balance(&creator_coldkey) >= initial_deposit, + Error::::LendingPoolNotEnoughBalanceToPayInitialDeposit + ); + + // Get the next pool id and increment it. + let pool_id = NextLendingPoolId::::get(); + NextLendingPoolId::::mutate(|id| *id = id.saturating_add(1)); + + // Derive the pool coldkey and hotkey. + let pool_coldkey = Self::get_lending_pool_coldkey(pool_id); + let _pool_hotkey = Self::get_lending_pool_hotkey(pool_id); + + LendingPools::::insert( + pool_id, + LendingPool { + creator: creator_coldkey.clone(), + initial_deposit, + max_lending_cap, + emissions_share, + }, + ); + + // Transfer the initial deposit from the creator coldkey to the pool coldkey. + T::Currency::transfer( + &creator_coldkey, + &pool_coldkey, + initial_deposit, + Preservation::Expendable, + )?; + + // Add initial deposit to individual pool contributions. + LendingPoolIndividualContributions::::mutate(pool_id, creator_coldkey, |contribution| { + *contribution = contribution.saturating_add(initial_deposit); + }); + // Add initial deposit to total pool contributions. + LendingPoolTotalContributions::::mutate(pool_id, |total| { + *total = total.saturating_add(initial_deposit); + }); + + Ok(()) + } + + pub fn get_lending_pool_coldkey(pool_id: u32) -> T::AccountId { + let entropy = (b"subtensor/lending_pool/cold/", pool_id).using_encoded(blake2_256); + let key = T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed"); + + key + } + + pub fn get_lending_pool_hotkey(pool_id: u32) -> T::AccountId { + let entropy = (b"subtensor/lending_pool/hot/", pool_id).using_encoded(blake2_256); + let key = T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed"); + + key + } +} + +// fn edit_lending_proposal_cut() {} + +// fn edit_lending_proposal_cap() {} + +// fn edit_lending_proposal_end() {} + +// maximum of pools for a specific user? +// // - minimum contribution bound +// // - if not already contributed, add as lender +// // - if already contributed, add to lending amount +// fn participate_to_lending_proposal(origin: (), cut: (), cap: (), end: ()) {} + +// // The owner of the proposal can call this extrinsic to finalize the +// // proposal, it will be checked if the pooled fund are enough to register +// // for a subnet, then it will register the subnet +// fn finalize_lending_proposal() {} + +// // When emission are received by the lend pool, distribute the cut of the subnet owner +// // to the lenders by sending the alpha to the ema price so lenders receive TAO. +// fn hook_on_emission() {} + +// // When on lend end, transfer ownership of the subnet to the subnet operator. +// fn hook_on_lease_end() {} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 03438aa637..8c785989fd 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -39,6 +39,7 @@ mod benchmarks; // ========================= pub mod coinbase; pub mod epoch; +pub mod lending_pools; pub mod macros; pub mod migrations; pub mod rpc_info; @@ -77,7 +78,7 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use pallet_drand::types::RoundNumber; - use sp_core::{ConstU32, H256}; + use sp_core::{ConstU32, ConstU64, H256}; use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; use sp_std::collections::vec_deque::VecDeque; use sp_std::vec; @@ -268,6 +269,21 @@ pub mod pallet { /// Additional information about the subnet pub additional: Vec, } + + /// Data structure for a subnet lending pool + // #[crate::freeze_struct("27945e6b9277e22b")] + #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] + pub struct LendingPool { + /// The creator of the pool and future owner of the subnet + pub creator: AccountId, + /// Initial deposit from the creator + pub initial_deposit: u64, + /// Hard cap for the fundraising + pub max_lending_cap: u64, + /// Share of future subnet emissions distributed to lenders + pub emissions_share: u64, + } + /// ============================ /// ==== Staking + Accounts ==== /// ============================ @@ -1546,6 +1562,33 @@ pub mod pallet { OptionQuery, >; + /// ===================================== + /// ==== Subnet Lending Pool Storage ==== + /// ===================================== + /// + /// All lending pools. + /// MAP (pool_id) -> The lending pool with the given pool_id. + #[pallet::storage] + pub type LendingPools = + StorageMap<_, Identity, u32, LendingPool, OptionQuery>; + /// + /// Next lending pool id. + /// ITEM(pool_id) + #[pallet::storage] + pub type NextLendingPoolId = StorageValue<_, u32, ValueQuery, ConstU32<0>>; + + /// Individual contributions to each lending pool. + /// MAP (pool_id, contributor_coldkey) -> u64 + #[pallet::storage] + pub type LendingPoolIndividualContributions = + StorageDoubleMap<_, Identity, u32, Identity, T::AccountId, u64, ValueQuery, ConstU64<0>>; + + /// Total contributions to each lending pool. + /// MAP (pool_id) -> u64 + #[pallet::storage] + pub type LendingPoolTotalContributions = + StorageMap<_, Identity, u32, u64, ValueQuery, ConstU64<0>>; + /// ================== /// ==== Genesis ===== /// ================== diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index af448c8771..a9eeba0fc9 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -5,6 +5,7 @@ use frame_support::pallet_macros::pallet_section; /// This can later be imported into the pallet using [`import_section`]. #[pallet_section] mod config { + /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config + pallet_drand::Config { @@ -210,5 +211,17 @@ mod config { /// Initial EMA price halving period #[pallet::constant] type InitialEmaPriceHalvingPeriod: Get; + /// Maximum number of lending pools + #[pallet::constant] + type LendingPoolsLimit: Get; + /// Minimum initial deposit for a lending pool + #[pallet::constant] + type LendingPoolMinInitialDeposit: Get; + /// Maximum funding cap for a lending pool + #[pallet::constant] + type LendingPoolMaxLendingCap: Get; + /// Minimum emissions share for a lending pool + #[pallet::constant] + type LendingPoolMinEmissionsShare: Get; } } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index bcd2bb33f5..127746a85b 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1909,5 +1909,22 @@ mod dispatches { Ok(()) } + + /// Create a new lending pool for a subnet. + #[pallet::call_index(92)] + #[pallet::weight(0)] + pub fn create_subnet_lending_pool( + origin: OriginFor, + initial_deposit: u64, + max_lending_cap: u64, + emissions_share: u64, + ) -> DispatchResult { + Self::do_create_subnet_lending_pool( + origin, + initial_deposit, + max_lending_cap, + emissions_share, + ) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 1f189cd2f6..ba7284534a 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -195,5 +195,19 @@ mod errors { ActivityCutoffTooLow, /// Call is disabled CallDisabled, + /// Maximum number of lending pools reached. + LendingPoolsLimitReached, + /// Lending pool initial deposit is too low. + LendingPoolInitialDepositTooLow, + /// Lending pool lending cap is inferior to initial deposit. + LendingPoolLendingCapInferiorToInitialDeposit, + /// Lending pool lending cap is too high. + LendingPoolLendingCapTooHigh, + /// Lending pool emissions share is too low. + LendingPoolEmissionsShareTooLow, + /// Lending pool emissions share is too high. + LendingPoolEmissionsShareTooHigh, + /// Lending pool creator coldkey does not have enough balance to pay initial deposit. + LendingPoolNotEnoughBalanceToPayInitialDeposit, } } diff --git a/pallets/subtensor/src/tests/lending_pools.rs b/pallets/subtensor/src/tests/lending_pools.rs new file mode 100644 index 0000000000..9d01b2d529 --- /dev/null +++ b/pallets/subtensor/src/tests/lending_pools.rs @@ -0,0 +1,221 @@ +use crate::*; +use frame_support::{assert_err, assert_ok}; +use frame_system::RawOrigin; +use sp_core::U256; + +use super::mock::*; + +#[test] +fn test_create_subnet_lending_pool_successfully() { + new_test_ext(1).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_balance = 5_000_000_000; // 5 TAO + let initial_deposit = 2_000_000_000; // 2 TAO + let max_lending_cap = 100_000_000_000; // 100 TAO + let emissions_share = 10; // 10% + + SubtensorModule::add_balance_to_coldkey_account(&creator_coldkey, initial_balance); + + assert_ok!(SubtensorModule::create_subnet_lending_pool( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + max_lending_cap, + emissions_share, + )); + + // Check that the pool was created successfully. + assert_eq!( + LendingPools::::get(0), + Some(LendingPool { + creator: creator_coldkey.clone(), + initial_deposit, + max_lending_cap, + emissions_share, + }) + ); + // Check that the creator coldkey was debited the initial deposit. + assert_eq!( + SubtensorModule::get_coldkey_balance(&creator_coldkey), + initial_balance - initial_deposit + ); + // Check that the pool coldkey was credited the initial deposit. + let pool_id = 0; // the first pool to be created has id 0 + let pool_coldkey = SubtensorModule::get_lending_pool_coldkey(pool_id); + assert_eq!( + SubtensorModule::get_coldkey_balance(&pool_coldkey), + initial_deposit + ); + // Check that the initial deposit was added to the individual contributions. + assert_eq!( + LendingPoolIndividualContributions::::get(pool_id, creator_coldkey), + initial_deposit + ); + // Check that the total contributions to the pool are equal to the initial deposit. + assert_eq!( + LendingPoolTotalContributions::::get(pool_id), + initial_deposit + ); + }); +} + +#[test] +fn test_create_subnet_lending_pool_fails_if_bad_origin() { + new_test_ext(1).execute_with(|| { + let initial_deposit = 2_000_000_000; // 2 TAO + let max_lending_cap = 100_000_000_000; // 100 TAO + let emissions_share = 10; // 10% + + assert_err!( + SubtensorModule::create_subnet_lending_pool( + RawOrigin::None.into(), + initial_deposit, + max_lending_cap, + emissions_share + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_create_subnet_lending_pool_fails_if_pool_limit_reached() { + new_test_ext(1).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 2_000_000_000; // 2 TAO + let max_lending_cap = 100_000_000_000; // 100 TAO + let emissions_share = 10; // 10% + + // Simulate the fact that we have reached the maximum number of lending pools. + NextLendingPoolId::::set(5); + + assert_err!( + SubtensorModule::create_subnet_lending_pool( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + max_lending_cap, + emissions_share + ), + Error::::LendingPoolsLimitReached + ); + }); +} + +#[test] +fn test_create_subnet_lending_pool_fails_if_initial_deposit_too_low() { + new_test_ext(1).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 500_000_000; // 0.5 TAO + let max_lending_cap = 100_000_000_000; // 100 TAO + let emissions_share = 10; // 10% + + assert_err!( + SubtensorModule::create_subnet_lending_pool( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + max_lending_cap, + emissions_share + ), + Error::::LendingPoolInitialDepositTooLow + ); + }); +} + +#[test] +fn test_create_subnet_lending_pool_fails_if_lending_cap_inferior_to_initial_deposit() { + new_test_ext(1).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 5_000_000_000; // 5 TAO + let max_lending_cap = 4_000_000_000; // 4 TAO + let emissions_share = 10; // 10% + + assert_err!( + SubtensorModule::create_subnet_lending_pool( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + max_lending_cap, + emissions_share + ), + Error::::LendingPoolLendingCapInferiorToInitialDeposit + ); + }); +} + +#[test] +fn test_create_subnet_lending_pool_fails_if_lending_cap_too_high() { + new_test_ext(1).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 2_000_000_000; // 2 TAO + let max_lending_cap = 2_000_000_000_000; // 2000 TAO + let emissions_share = 10; // 10% + + assert_err!( + SubtensorModule::create_subnet_lending_pool( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + max_lending_cap, + emissions_share + ), + Error::::LendingPoolLendingCapTooHigh + ); + }); +} + +#[test] +fn test_create_subnet_lending_pool_fails_if_emissions_share_too_low() { + new_test_ext(1).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 2_000_000_000; // 2 TAO + let max_lending_cap = 100_000_000_000; // 100 TAO + let emissions_share = 4; // 4% + + assert_err!( + SubtensorModule::create_subnet_lending_pool( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + max_lending_cap, + emissions_share + ), + Error::::LendingPoolEmissionsShareTooLow + ); + }); +} + +#[test] +fn test_create_subnet_lending_pool_fails_if_emissions_share_too_high() { + new_test_ext(1).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 2_000_000_000; // 2 TAO + let max_lending_cap = 100_000_000_000; // 100 TAO + let emissions_share = 101; // 101% + + assert_err!( + SubtensorModule::create_subnet_lending_pool( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + max_lending_cap, + emissions_share + ), + Error::::LendingPoolEmissionsShareTooHigh + ); + }); +} + +#[test] +fn create_subnet_lending_pool_fails_if_creator_coldkey_does_not_contains_initial_deposit() { + new_test_ext(1).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 2_000_000_000; // 2 TAO + let max_lending_cap = 100_000_000_000; // 100 TAO + let emissions_share = 10; // 10% + + assert_err!( + SubtensorModule::create_subnet_lending_pool( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + max_lending_cap, + emissions_share + ), + Error::::LendingPoolNotEnoughBalanceToPayInitialDeposit + ); + }); +} diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 0d979a6126..65c0a5f5e9 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -185,6 +185,10 @@ parameter_types! { pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks + pub const LendingPoolsLimit: u32 = 5; // 5 lending pools + pub const LendingPoolMinInitialDeposit: u64 = 1_000_000_000; // 1 TAO + pub const LendingPoolMaxLendingCap: u64 = 1_000_000_000_000; // 1000 TAO + pub const LendingPoolMinEmissionsShare: u64 = 5; // 5% } // Configure collective pallet for council @@ -408,6 +412,10 @@ impl crate::Config for Test { type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; + type LendingPoolsLimit = LendingPoolsLimit; + type LendingPoolMinInitialDeposit = LendingPoolMinInitialDeposit; + type LendingPoolMaxLendingCap = LendingPoolMaxLendingCap; + type LendingPoolMinEmissionsShare = LendingPoolMinEmissionsShare; } pub struct OriginPrivilegeCmp; diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 6865c9fa49..aa8b22f4b3 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -5,6 +5,7 @@ mod delegate_info; mod difficulty; mod emission; mod epoch; +mod lending_pools; mod math; mod migration; mod mock; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 25799a75c1..e27589b674 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1018,6 +1018,10 @@ parameter_types! { pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks + pub const LendingPoolsLimit: u32 = 10_000; // 10 000 lending pools + pub const LendingPoolMinInitialDeposit: u64 = 10_000_000_000; // 10 TAO + pub const LendingPoolMaxLendingCap: u64 = 100_000_000_000_000; // 100 000 TAO + pub const LendingPoolMinEmissionsShare: u64 = 5; // 5% } impl pallet_subtensor::Config for Runtime { @@ -1082,6 +1086,10 @@ impl pallet_subtensor::Config for Runtime { type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; + type LendingPoolsLimit = LendingPoolsLimit; + type LendingPoolMinInitialDeposit = LendingPoolMinInitialDeposit; + type LendingPoolMaxLendingCap = LendingPoolMaxLendingCap; + type LendingPoolMinEmissionsShare = LendingPoolMinEmissionsShare; } use sp_runtime::BoundedVec; From a52b4abcbd04a2b243d87434b34917ccd29e3131 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 27 Mar 2025 19:45:40 +0100 Subject: [PATCH 02/70] cargo clippy --- pallets/subtensor/src/tests/lending_pools.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/lending_pools.rs b/pallets/subtensor/src/tests/lending_pools.rs index 9d01b2d529..ec10ab1c24 100644 --- a/pallets/subtensor/src/tests/lending_pools.rs +++ b/pallets/subtensor/src/tests/lending_pools.rs @@ -27,7 +27,7 @@ fn test_create_subnet_lending_pool_successfully() { assert_eq!( LendingPools::::get(0), Some(LendingPool { - creator: creator_coldkey.clone(), + creator: creator_coldkey, initial_deposit, max_lending_cap, emissions_share, From 3a8710156bc025f8d2490fd0fc8a6d87b261272e Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 27 Mar 2025 19:46:21 +0100 Subject: [PATCH 03/70] cargo fmt --- pallets/subtensor/src/macros/dispatches.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 127746a85b..d8188e71a8 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1912,7 +1912,7 @@ mod dispatches { /// Create a new lending pool for a subnet. #[pallet::call_index(92)] - #[pallet::weight(0)] + #[pallet::weight(0)] pub fn create_subnet_lending_pool( origin: OriginFor, initial_deposit: u64, From db286244cc7f1bfd4d566e340118da05bbfb53a2 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 28 Mar 2025 14:43:33 +0100 Subject: [PATCH 04/70] refacto to crowdloan instead of lending pool --- pallets/subtensor/src/crowdloan/mod.rs | 190 +++++++++++ pallets/subtensor/src/lending_pools/mod.rs | 128 -------- pallets/subtensor/src/lib.rs | 14 +- pallets/subtensor/src/macros/config.rs | 6 + pallets/subtensor/src/macros/dispatches.rs | 18 +- pallets/subtensor/src/macros/errors.rs | 20 +- pallets/subtensor/src/tests/crowdloan.rs | 314 +++++++++++++++++++ pallets/subtensor/src/tests/lending_pools.rs | 221 ------------- pallets/subtensor/src/tests/mock.rs | 4 + pallets/subtensor/src/tests/mod.rs | 2 +- 10 files changed, 541 insertions(+), 376 deletions(-) create mode 100644 pallets/subtensor/src/crowdloan/mod.rs delete mode 100644 pallets/subtensor/src/lending_pools/mod.rs create mode 100644 pallets/subtensor/src/tests/crowdloan.rs delete mode 100644 pallets/subtensor/src/tests/lending_pools.rs diff --git a/pallets/subtensor/src/crowdloan/mod.rs b/pallets/subtensor/src/crowdloan/mod.rs new file mode 100644 index 0000000000..59e29cecb0 --- /dev/null +++ b/pallets/subtensor/src/crowdloan/mod.rs @@ -0,0 +1,190 @@ +// This file is heavily inspired by Polkadot's Crowdloan implementation: +// https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/runtime/common/src/crowdloan/mod.rs + +use super::*; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_io::hashing::blake2_256; +use sp_runtime::{ + Percent, + traits::{Saturating, TrailingZeroInput}, +}; + +impl Pallet { + pub fn do_create_subnet_crowdloan( + origin: T::RuntimeOrigin, + initial_deposit: u64, + cap: u64, + emissions_share: Percent, + end: BlockNumberFor, + ) -> dispatch::DispatchResult { + let creator = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + + // Ensure crowdloan cannot end in the past. + ensure!(end > now, Error::::CrowdloanCannotEndInPast); + + // Ensure crowdloan duration is at least the minimum required. + let duration = end.saturating_sub(now); + ensure!( + duration > T::MinCrowdloanBlocksDuration::get(), + Error::::CrowdloanBlocksDurationTooShort + ); + + // Ensure the initial deposit is at least the minimum required. + ensure!( + initial_deposit >= T::MinCrowdloanInitialDeposit::get(), + Error::::CrowdloanInitialDepositTooLow + ); + + // Ensure the cap is more than the initial deposit. + ensure!( + cap > initial_deposit, + Error::::CrowdloanCapInferiorToInitialDeposit + ); + + let crowdloan_index = NextSubnetCrowdloanIndex::::get(); + + // let new_crowdfund_index = crowdloan_index.checked_add(1).ok_or(Error::::Overflow)?; + + // An existing crowdloan with the same index should not exist. + // ensure!( + // !SubnetCrowdloans::::contains_key(crowdloan_index), + // Error::::CrowdloanIndexTaken, + // ); + + // // Ensure we have reached the maximum number of lending pools + // ensure!( + // NextLendingPoolId::::get() < T::LendingPoolsLimit::get(), + // Error::::LendingPoolsLimitReached + // ); + // // Ensure the initial deposit is above the minimum required to create a lending pool. + // ensure!( + // initial_deposit >= T::LendingPoolMinInitialDeposit::get(), + // Error::::LendingPoolInitialDepositTooLow + // ); + // // Ensure the max lending cap is at least superior to the initial deposit. + // ensure!( + // max_lending_cap > initial_deposit, + // Error::::LendingPoolLendingCapInferiorToInitialDeposit + // ); + // // Ensure the max lending cap is not greater than the maximum allowed. + // ensure!( + // max_lending_cap <= T::LendingPoolMaxLendingCap::get(), + // Error::::LendingPoolLendingCapTooHigh + // ); + // // Ensure the emisions share is at a minimum of some value. + // ensure!( + // emissions_share >= T::LendingPoolMinEmissionsShare::get(), + // Error::::LendingPoolEmissionsShareTooLow + // ); + // // Ensure the emissions share is not greater than 100%. + // ensure!( + // emissions_share <= 100, + // Error::::LendingPoolEmissionsShareTooHigh + // ); + // // Ensure creator coldkey contains the initial deposit. + // ensure!( + // Self::get_coldkey_balance(&creator_coldkey) >= initial_deposit, + // Error::::LendingPoolNotEnoughBalanceToPayInitialDeposit + // ); + + // // Get the next pool id and increment it. + // let pool_id = NextLendingPoolId::::get(); + // NextLendingPoolId::::mutate(|id| *id = id.saturating_add(1)); + + // // Derive the pool coldkey and hotkey. + // let pool_coldkey = Self::get_lending_pool_coldkey(pool_id); + // let _pool_hotkey = Self::get_lending_pool_hotkey(pool_id); + + // LendingPools::::insert( + // pool_id, + // LendingPool { + // creator: creator_coldkey.clone(), + // initial_deposit, + // cap, + // emissions_share, + // }, + // ); + + // // Transfer the initial deposit from the creator coldkey to the pool coldkey. + // T::Currency::transfer( + // &creator_coldkey, + // &pool_coldkey, + // initial_deposit, + // Preservation::Expendable, + // )?; + + // // Add initial deposit to individual pool contributions. + // LendingPoolIndividualContributions::::mutate(pool_id, creator_coldkey, |contribution| { + // *contribution = contribution.saturating_add(initial_deposit); + // }); + // // Add initial deposit to total pool contributions. + // LendingPoolTotalContributions::::mutate(pool_id, |total| { + // *total = total.saturating_add(initial_deposit); + // }); + + Ok(()) + } + + // pub fn do_contribute_to_subnet_crowdloan( + // origin: T::RuntimeOrigin, + // pool_id: u32, + // amount: u64, + // ) -> dispatch::DispatchResult { + // let contributor_coldkey = ensure_signed(origin)?; + + // let lending_pool = + // LendingPools::::get(pool_id).ok_or(Error::::LendingPoolDoesNotExist)?; + + // // Ensure the contributor has enough balance to contribute. + // ensure!( + // Self::get_coldkey_balance(&contributor_coldkey) >= amount, + // Error::::NotEnoughBalanceToContributeToLendingPool + // ); + + // // Ensure the lending pool has not reached its max lending cap. + // let total_contributions = LendingPoolTotalContributions::::get(pool_id); + + // Ok(()) + // } + + pub fn get_crowdloan_coldkey(crowdloan_index: u32) -> T::AccountId { + let entropy = (b"subtensor/crowdloan/cold/", crowdloan_index).using_encoded(blake2_256); + let key = T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed"); + + key + } + + pub fn get_crowdloan_hotkey(crowdloan_index: u32) -> T::AccountId { + let entropy = (b"subtensor/crowdloan/hot/", crowdloan_index).using_encoded(blake2_256); + let key = T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed"); + + key + } +} + +// fn edit_lending_proposal_cut() {} + +// fn edit_lending_proposal_cap() {} + +// fn edit_lending_proposal_end() {} + +// maximum of pools for a specific user? +// // - minimum contribution bound +// // - if not already contributed, add as lender +// // - if already contributed, add to lending amount +// fn participate_to_lending_proposal(origin: (), cut: (), cap: (), end: ()) {} + +// // The owner of the proposal can call this extrinsic to finalize the +// // proposal, it will be checked if the pooled fund are enough to register +// // for a subnet, then it will register the subnet +// fn finalize_lending_proposal() {} + +// // When emission are received by the lend pool, distribute the cut of the subnet owner +// // to the lenders by sending the alpha to the ema price so lenders receive TAO. +// fn hook_on_emission() {} + +// // When on lend end, transfer ownership of the subnet to the subnet operator. +// fn hook_on_lease_end() {} diff --git a/pallets/subtensor/src/lending_pools/mod.rs b/pallets/subtensor/src/lending_pools/mod.rs deleted file mode 100644 index 351e546de3..0000000000 --- a/pallets/subtensor/src/lending_pools/mod.rs +++ /dev/null @@ -1,128 +0,0 @@ -use super::*; -use frame_support::traits::{fungible::Mutate, tokens::Preservation}; -use sp_io::hashing::blake2_256; -use sp_runtime::traits::TrailingZeroInput; - -impl Pallet { - pub fn do_create_subnet_lending_pool( - origin: T::RuntimeOrigin, - initial_deposit: u64, - max_lending_cap: u64, - emissions_share: u64, - ) -> dispatch::DispatchResult { - let creator_coldkey = ensure_signed(origin)?; - - // Ensure we have reached the maximum number of lending pools - ensure!( - NextLendingPoolId::::get() < T::LendingPoolsLimit::get(), - Error::::LendingPoolsLimitReached - ); - // Ensure the initial deposit is above the minimum required to create a lending pool. - ensure!( - initial_deposit >= T::LendingPoolMinInitialDeposit::get(), - Error::::LendingPoolInitialDepositTooLow - ); - // Ensure the max lending cap is at least superior to the initial deposit. - ensure!( - max_lending_cap > initial_deposit, - Error::::LendingPoolLendingCapInferiorToInitialDeposit - ); - // Ensure the max lending cap is not greater than the maximum allowed. - ensure!( - max_lending_cap <= T::LendingPoolMaxLendingCap::get(), - Error::::LendingPoolLendingCapTooHigh - ); - // Ensure the emisions share is at a minimum of some value. - ensure!( - emissions_share >= T::LendingPoolMinEmissionsShare::get(), - Error::::LendingPoolEmissionsShareTooLow - ); - // Ensure the emissions share is not greater than 100%. - ensure!( - emissions_share <= 100, - Error::::LendingPoolEmissionsShareTooHigh - ); - // Ensure creator coldkey contains the initial deposit. - ensure!( - Self::get_coldkey_balance(&creator_coldkey) >= initial_deposit, - Error::::LendingPoolNotEnoughBalanceToPayInitialDeposit - ); - - // Get the next pool id and increment it. - let pool_id = NextLendingPoolId::::get(); - NextLendingPoolId::::mutate(|id| *id = id.saturating_add(1)); - - // Derive the pool coldkey and hotkey. - let pool_coldkey = Self::get_lending_pool_coldkey(pool_id); - let _pool_hotkey = Self::get_lending_pool_hotkey(pool_id); - - LendingPools::::insert( - pool_id, - LendingPool { - creator: creator_coldkey.clone(), - initial_deposit, - max_lending_cap, - emissions_share, - }, - ); - - // Transfer the initial deposit from the creator coldkey to the pool coldkey. - T::Currency::transfer( - &creator_coldkey, - &pool_coldkey, - initial_deposit, - Preservation::Expendable, - )?; - - // Add initial deposit to individual pool contributions. - LendingPoolIndividualContributions::::mutate(pool_id, creator_coldkey, |contribution| { - *contribution = contribution.saturating_add(initial_deposit); - }); - // Add initial deposit to total pool contributions. - LendingPoolTotalContributions::::mutate(pool_id, |total| { - *total = total.saturating_add(initial_deposit); - }); - - Ok(()) - } - - pub fn get_lending_pool_coldkey(pool_id: u32) -> T::AccountId { - let entropy = (b"subtensor/lending_pool/cold/", pool_id).using_encoded(blake2_256); - let key = T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed"); - - key - } - - pub fn get_lending_pool_hotkey(pool_id: u32) -> T::AccountId { - let entropy = (b"subtensor/lending_pool/hot/", pool_id).using_encoded(blake2_256); - let key = T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed"); - - key - } -} - -// fn edit_lending_proposal_cut() {} - -// fn edit_lending_proposal_cap() {} - -// fn edit_lending_proposal_end() {} - -// maximum of pools for a specific user? -// // - minimum contribution bound -// // - if not already contributed, add as lender -// // - if already contributed, add to lending amount -// fn participate_to_lending_proposal(origin: (), cut: (), cap: (), end: ()) {} - -// // The owner of the proposal can call this extrinsic to finalize the -// // proposal, it will be checked if the pooled fund are enough to register -// // for a subnet, then it will register the subnet -// fn finalize_lending_proposal() {} - -// // When emission are received by the lend pool, distribute the cut of the subnet owner -// // to the lenders by sending the alpha to the ema price so lenders receive TAO. -// fn hook_on_emission() {} - -// // When on lend end, transfer ownership of the subnet to the subnet operator. -// fn hook_on_lease_end() {} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 8c785989fd..d54e3c2dae 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -38,8 +38,8 @@ mod benchmarks; // ==== Pallet Imports ===== // ========================= pub mod coinbase; +pub mod crowdloan; pub mod epoch; -pub mod lending_pools; pub mod macros; pub mod migrations; pub mod rpc_info; @@ -1563,19 +1563,19 @@ pub mod pallet { >; /// ===================================== - /// ==== Subnet Lending Pool Storage ==== + /// ==== Subnet Crowdloan Storage ==== /// ===================================== /// - /// All lending pools. - /// MAP (pool_id) -> The lending pool with the given pool_id. + /// All subnet crowdloans. + /// MAP (crowdloan_id) -> The crowdloan with the given crowdloan_id. #[pallet::storage] - pub type LendingPools = + pub type SubnetCrowdloans = StorageMap<_, Identity, u32, LendingPool, OptionQuery>; /// - /// Next lending pool id. + /// Next subnet crowdloan id. /// ITEM(pool_id) #[pallet::storage] - pub type NextLendingPoolId = StorageValue<_, u32, ValueQuery, ConstU32<0>>; + pub type NextSubnetCrowdloanIndex = StorageValue<_, u32, ValueQuery, ConstU32<0>>; /// Individual contributions to each lending pool. /// MAP (pool_id, contributor_coldkey) -> u64 diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index a9eeba0fc9..ceddafc19a 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -211,6 +211,12 @@ mod config { /// Initial EMA price halving period #[pallet::constant] type InitialEmaPriceHalvingPeriod: Get; + /// Minimum crowdloan blocks duration + #[pallet::constant] + type MinCrowdloanBlocksDuration: Get>; + /// Minimum initial deposit for a crowdloan + #[pallet::constant] + type MinCrowdloanInitialDeposit: Get; /// Maximum number of lending pools #[pallet::constant] type LendingPoolsLimit: Get; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index d8188e71a8..04340b58a2 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -9,7 +9,7 @@ mod dispatches { use frame_support::traits::schedule::DispatchTime; use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_system::pallet_prelude::BlockNumberFor; - use sp_runtime::traits::Saturating; + use sp_runtime::{Percent, traits::Saturating}; use crate::MAX_CRV3_COMMIT_SIZE_BYTES; /// Dispatchable functions allow users to interact with the pallet and invoke state changes. @@ -1910,21 +1910,17 @@ mod dispatches { Ok(()) } - /// Create a new lending pool for a subnet. + /// Create a new crowdloan for a subnet. #[pallet::call_index(92)] #[pallet::weight(0)] - pub fn create_subnet_lending_pool( + pub fn create_subnet_crowdloan( origin: OriginFor, initial_deposit: u64, - max_lending_cap: u64, - emissions_share: u64, + cap: u64, + emissions_share: Percent, + end: BlockNumberFor, ) -> DispatchResult { - Self::do_create_subnet_lending_pool( - origin, - initial_deposit, - max_lending_cap, - emissions_share, - ) + Self::do_create_subnet_crowdloan(origin, initial_deposit, cap, emissions_share, end) } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index ba7284534a..947d2967fa 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -195,19 +195,23 @@ mod errors { ActivityCutoffTooLow, /// Call is disabled CallDisabled, - /// Maximum number of lending pools reached. - LendingPoolsLimitReached, - /// Lending pool initial deposit is too low. - LendingPoolInitialDepositTooLow, - /// Lending pool lending cap is inferior to initial deposit. - LendingPoolLendingCapInferiorToInitialDeposit, - /// Lending pool lending cap is too high. - LendingPoolLendingCapTooHigh, + /// Crowdloan cannot end in the past. + CrowdloanCannotEndInPast, + /// Crowdloan blocks duration is too short. + CrowdloanBlocksDurationTooShort, + /// Crowdloan initial deposit is too low. + CrowdloanInitialDepositTooLow, + /// Crowdloan cap is inferior to initial deposit. + CrowdloanCapInferiorToInitialDeposit, /// Lending pool emissions share is too low. LendingPoolEmissionsShareTooLow, /// Lending pool emissions share is too high. LendingPoolEmissionsShareTooHigh, /// Lending pool creator coldkey does not have enough balance to pay initial deposit. LendingPoolNotEnoughBalanceToPayInitialDeposit, + /// Lending pool does not exist. + LendingPoolDoesNotExist, + /// Not enough balance to contribute to lending pool. + NotEnoughBalanceToContributeToLendingPool, } } diff --git a/pallets/subtensor/src/tests/crowdloan.rs b/pallets/subtensor/src/tests/crowdloan.rs new file mode 100644 index 0000000000..509c225569 --- /dev/null +++ b/pallets/subtensor/src/tests/crowdloan.rs @@ -0,0 +1,314 @@ +use crate::*; +use frame_support::{assert_err, assert_ok, traits::Currency}; +use frame_system::RawOrigin; +use sp_core::U256; +use sp_runtime::Percent; + +use super::mock::*; + +// #[test] +// fn test_create_subnet_lending_pool_successfully() { +// new_test_ext(1).execute_with(|| { +// let creator_coldkey = U256::from(1); +// let initial_balance = 5_000_000_000; // 5 TAO +// let initial_deposit = 2_000_000_000; // 2 TAO +// let cap = 100_000_000_000; // 100 TAO +// let emissions_share = Percent::from_percent(10); +// let end: BlockNumber = 100; + +// SubtensorModule::add_balance_to_coldkey_account(&creator_coldkey, initial_balance); + +// assert_ok!(SubtensorModule::create_subnet_crowdloan( +// RuntimeOrigin::signed(creator_coldkey), +// initial_deposit, +// cap, +// emissions_share, +// end +// )); + +// // // Check that the pool was created successfully. +// // assert_eq!( +// // LendingPools::::get(0), +// // Some(LendingPool { +// // creator: creator_coldkey, +// // initial_deposit, +// // max_lending_cap, +// // emissions_share, +// // }) +// // ); +// // // Check that the creator coldkey was debited the initial deposit. +// // assert_eq!( +// // SubtensorModule::get_coldkey_balance(&creator_coldkey), +// // initial_balance - initial_deposit +// // ); +// // // Check that the pool coldkey was credited the initial deposit. +// // let pool_id = 0; // the first pool to be created has id 0 +// // let pool_coldkey = SubtensorModule::get_lending_pool_coldkey(pool_id); +// // assert_eq!( +// // SubtensorModule::get_coldkey_balance(&pool_coldkey), +// // initial_deposit +// // ); +// // // Check that the initial deposit was added to the individual contributions. +// // assert_eq!( +// // LendingPoolIndividualContributions::::get(pool_id, creator_coldkey), +// // initial_deposit +// // ); +// // // Check that the total contributions to the pool are equal to the initial deposit. +// // assert_eq!( +// // LendingPoolTotalContributions::::get(pool_id), +// // initial_deposit +// // ); +// }); +// } + +#[test] +fn test_create_subnet_crowdloan_fails_if_bad_origin() { + new_test_ext(1).execute_with(|| { + let initial_deposit = 2_000_000_000; // 2 TAO + let cap = 100_000_000_000; // 100 TAO + let emissions_share = Percent::from_percent(10); + let end = frame_system::Pallet::::block_number() + 50; // 50 blocks from now + + assert_err!( + SubtensorModule::create_subnet_crowdloan( + RawOrigin::None.into(), + initial_deposit, + cap, + emissions_share, + end + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_create_subnet_crowdloan_fails_if_end_is_in_the_past() { + new_test_ext(10).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 2_000_000_000; // 2 TAO + let cap = 100_000_000_000; // 100 TAO + let emissions_share = Percent::from_percent(10); + let end = frame_system::Pallet::::block_number() - 1; // 1 block in the past + + assert_err!( + SubtensorModule::create_subnet_crowdloan( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + cap, + emissions_share, + end + ), + Error::::CrowdloanCannotEndInPast + ) + }); +} + +#[test] +fn test_create_subnet_crowdloan_fails_if_duration_is_too_short() { + new_test_ext(10).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 2_000_000_000; // 2 TAO + let cap = 100_000_000_000; // 100 TAO + let emissions_share = Percent::from_percent(10); + let end = frame_system::Pallet::::block_number() + 5; // 5 blocks from now + + assert_err!( + SubtensorModule::create_subnet_crowdloan( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + cap, + emissions_share, + end + ), + Error::::CrowdloanBlocksDurationTooShort + ); + }); +} + +#[test] +fn test_create_subnet_crowdloan_fails_if_initial_deposit_is_too_low() { + new_test_ext(10).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 1_000_000_000; // 1 TAO + let cap = 100_000_000_000; // 100 TAO + let emissions_share = Percent::from_percent(10); + let end = frame_system::Pallet::::block_number() + 50; // 50 blocks from now + + assert_err!( + SubtensorModule::create_subnet_crowdloan( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + cap, + emissions_share, + end + ), + Error::::CrowdloanInitialDepositTooLow + ); + }) +} + +#[test] +fn test_create_subnet_crowdloan_fails_if_cap_is_inferior_to_initial_deposit() { + new_test_ext(10).execute_with(|| { + let creator_coldkey = U256::from(1); + let initial_deposit = 5_000_000_000; // 5 TAO + let cap = 4_000_000_000; // 4 TAO + let emissions_share = Percent::from_percent(10); + let end = frame_system::Pallet::::block_number() + 50; // 50 blocks from now + + assert_err!( + SubtensorModule::create_subnet_crowdloan( + RuntimeOrigin::signed(creator_coldkey), + initial_deposit, + cap, + emissions_share, + end + ), + Error::::CrowdloanCapInferiorToInitialDeposit + ); + }) +} + +// #[test] +// fn test_create_subnet_lending_pool_fails_if_pool_limit_reached() { +// new_test_ext(1).execute_with(|| { +// let creator_coldkey = U256::from(1); +// let initial_deposit = 2_000_000_000; // 2 TAO +// let max_lending_cap = 100_000_000_000; // 100 TAO +// let emissions_share = 10; // 10% + +// // Simulate the fact that we have reached the maximum number of lending pools. +// NextLendingPoolId::::set(5); + +// assert_err!( +// SubtensorModule::create_subnet_lending_pool( +// RuntimeOrigin::signed(creator_coldkey), +// initial_deposit, +// max_lending_cap, +// emissions_share +// ), +// Error::::LendingPoolsLimitReached +// ); +// }); +// } + +// #[test] +// fn test_create_subnet_lending_pool_fails_if_initial_deposit_too_low() { +// new_test_ext(1).execute_with(|| { +// let creator_coldkey = U256::from(1); +// let initial_deposit = 500_000_000; // 0.5 TAO +// let max_lending_cap = 100_000_000_000; // 100 TAO +// let emissions_share = 10; // 10% + +// assert_err!( +// SubtensorModule::create_subnet_lending_pool( +// RuntimeOrigin::signed(creator_coldkey), +// initial_deposit, +// max_lending_cap, +// emissions_share +// ), +// Error::::LendingPoolInitialDepositTooLow +// ); +// }); +// } + +// #[test] +// fn test_create_subnet_lending_pool_fails_if_lending_cap_inferior_to_initial_deposit() { +// new_test_ext(1).execute_with(|| { +// let creator_coldkey = U256::from(1); +// let initial_deposit = 5_000_000_000; // 5 TAO +// let max_lending_cap = 4_000_000_000; // 4 TAO +// let emissions_share = 10; // 10% + +// assert_err!( +// SubtensorModule::create_subnet_lending_pool( +// RuntimeOrigin::signed(creator_coldkey), +// initial_deposit, +// max_lending_cap, +// emissions_share +// ), +// Error::::LendingPoolLendingCapInferiorToInitialDeposit +// ); +// }); +// } + +// #[test] +// fn test_create_subnet_lending_pool_fails_if_lending_cap_too_high() { +// new_test_ext(1).execute_with(|| { +// let creator_coldkey = U256::from(1); +// let initial_deposit = 2_000_000_000; // 2 TAO +// let max_lending_cap = 2_000_000_000_000; // 2000 TAO +// let emissions_share = 10; // 10% + +// assert_err!( +// SubtensorModule::create_subnet_lending_pool( +// RuntimeOrigin::signed(creator_coldkey), +// initial_deposit, +// max_lending_cap, +// emissions_share +// ), +// Error::::LendingPoolLendingCapTooHigh +// ); +// }); +// } + +// #[test] +// fn test_create_subnet_lending_pool_fails_if_emissions_share_too_low() { +// new_test_ext(1).execute_with(|| { +// let creator_coldkey = U256::from(1); +// let initial_deposit = 2_000_000_000; // 2 TAO +// let max_lending_cap = 100_000_000_000; // 100 TAO +// let emissions_share = 4; // 4% + +// assert_err!( +// SubtensorModule::create_subnet_lending_pool( +// RuntimeOrigin::signed(creator_coldkey), +// initial_deposit, +// max_lending_cap, +// emissions_share +// ), +// Error::::LendingPoolEmissionsShareTooLow +// ); +// }); +// } + +// #[test] +// fn test_create_subnet_lending_pool_fails_if_emissions_share_too_high() { +// new_test_ext(1).execute_with(|| { +// let creator_coldkey = U256::from(1); +// let initial_deposit = 2_000_000_000; // 2 TAO +// let max_lending_cap = 100_000_000_000; // 100 TAO +// let emissions_share = 101; // 101% + +// assert_err!( +// SubtensorModule::create_subnet_lending_pool( +// RuntimeOrigin::signed(creator_coldkey), +// initial_deposit, +// max_lending_cap, +// emissions_share +// ), +// Error::::LendingPoolEmissionsShareTooHigh +// ); +// }); +// } + +// #[test] +// fn create_subnet_lending_pool_fails_if_creator_coldkey_does_not_contains_initial_deposit() { +// new_test_ext(1).execute_with(|| { +// let creator_coldkey = U256::from(1); +// let initial_deposit = 2_000_000_000; // 2 TAO +// let max_lending_cap = 100_000_000_000; // 100 TAO +// let emissions_share = 10; // 10% + +// assert_err!( +// SubtensorModule::create_subnet_lending_pool( +// RuntimeOrigin::signed(creator_coldkey), +// initial_deposit, +// max_lending_cap, +// emissions_share +// ), +// Error::::LendingPoolNotEnoughBalanceToPayInitialDeposit +// ); +// }); +// } diff --git a/pallets/subtensor/src/tests/lending_pools.rs b/pallets/subtensor/src/tests/lending_pools.rs deleted file mode 100644 index ec10ab1c24..0000000000 --- a/pallets/subtensor/src/tests/lending_pools.rs +++ /dev/null @@ -1,221 +0,0 @@ -use crate::*; -use frame_support::{assert_err, assert_ok}; -use frame_system::RawOrigin; -use sp_core::U256; - -use super::mock::*; - -#[test] -fn test_create_subnet_lending_pool_successfully() { - new_test_ext(1).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_balance = 5_000_000_000; // 5 TAO - let initial_deposit = 2_000_000_000; // 2 TAO - let max_lending_cap = 100_000_000_000; // 100 TAO - let emissions_share = 10; // 10% - - SubtensorModule::add_balance_to_coldkey_account(&creator_coldkey, initial_balance); - - assert_ok!(SubtensorModule::create_subnet_lending_pool( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - max_lending_cap, - emissions_share, - )); - - // Check that the pool was created successfully. - assert_eq!( - LendingPools::::get(0), - Some(LendingPool { - creator: creator_coldkey, - initial_deposit, - max_lending_cap, - emissions_share, - }) - ); - // Check that the creator coldkey was debited the initial deposit. - assert_eq!( - SubtensorModule::get_coldkey_balance(&creator_coldkey), - initial_balance - initial_deposit - ); - // Check that the pool coldkey was credited the initial deposit. - let pool_id = 0; // the first pool to be created has id 0 - let pool_coldkey = SubtensorModule::get_lending_pool_coldkey(pool_id); - assert_eq!( - SubtensorModule::get_coldkey_balance(&pool_coldkey), - initial_deposit - ); - // Check that the initial deposit was added to the individual contributions. - assert_eq!( - LendingPoolIndividualContributions::::get(pool_id, creator_coldkey), - initial_deposit - ); - // Check that the total contributions to the pool are equal to the initial deposit. - assert_eq!( - LendingPoolTotalContributions::::get(pool_id), - initial_deposit - ); - }); -} - -#[test] -fn test_create_subnet_lending_pool_fails_if_bad_origin() { - new_test_ext(1).execute_with(|| { - let initial_deposit = 2_000_000_000; // 2 TAO - let max_lending_cap = 100_000_000_000; // 100 TAO - let emissions_share = 10; // 10% - - assert_err!( - SubtensorModule::create_subnet_lending_pool( - RawOrigin::None.into(), - initial_deposit, - max_lending_cap, - emissions_share - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn test_create_subnet_lending_pool_fails_if_pool_limit_reached() { - new_test_ext(1).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 2_000_000_000; // 2 TAO - let max_lending_cap = 100_000_000_000; // 100 TAO - let emissions_share = 10; // 10% - - // Simulate the fact that we have reached the maximum number of lending pools. - NextLendingPoolId::::set(5); - - assert_err!( - SubtensorModule::create_subnet_lending_pool( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - max_lending_cap, - emissions_share - ), - Error::::LendingPoolsLimitReached - ); - }); -} - -#[test] -fn test_create_subnet_lending_pool_fails_if_initial_deposit_too_low() { - new_test_ext(1).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 500_000_000; // 0.5 TAO - let max_lending_cap = 100_000_000_000; // 100 TAO - let emissions_share = 10; // 10% - - assert_err!( - SubtensorModule::create_subnet_lending_pool( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - max_lending_cap, - emissions_share - ), - Error::::LendingPoolInitialDepositTooLow - ); - }); -} - -#[test] -fn test_create_subnet_lending_pool_fails_if_lending_cap_inferior_to_initial_deposit() { - new_test_ext(1).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 5_000_000_000; // 5 TAO - let max_lending_cap = 4_000_000_000; // 4 TAO - let emissions_share = 10; // 10% - - assert_err!( - SubtensorModule::create_subnet_lending_pool( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - max_lending_cap, - emissions_share - ), - Error::::LendingPoolLendingCapInferiorToInitialDeposit - ); - }); -} - -#[test] -fn test_create_subnet_lending_pool_fails_if_lending_cap_too_high() { - new_test_ext(1).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 2_000_000_000; // 2 TAO - let max_lending_cap = 2_000_000_000_000; // 2000 TAO - let emissions_share = 10; // 10% - - assert_err!( - SubtensorModule::create_subnet_lending_pool( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - max_lending_cap, - emissions_share - ), - Error::::LendingPoolLendingCapTooHigh - ); - }); -} - -#[test] -fn test_create_subnet_lending_pool_fails_if_emissions_share_too_low() { - new_test_ext(1).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 2_000_000_000; // 2 TAO - let max_lending_cap = 100_000_000_000; // 100 TAO - let emissions_share = 4; // 4% - - assert_err!( - SubtensorModule::create_subnet_lending_pool( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - max_lending_cap, - emissions_share - ), - Error::::LendingPoolEmissionsShareTooLow - ); - }); -} - -#[test] -fn test_create_subnet_lending_pool_fails_if_emissions_share_too_high() { - new_test_ext(1).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 2_000_000_000; // 2 TAO - let max_lending_cap = 100_000_000_000; // 100 TAO - let emissions_share = 101; // 101% - - assert_err!( - SubtensorModule::create_subnet_lending_pool( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - max_lending_cap, - emissions_share - ), - Error::::LendingPoolEmissionsShareTooHigh - ); - }); -} - -#[test] -fn create_subnet_lending_pool_fails_if_creator_coldkey_does_not_contains_initial_deposit() { - new_test_ext(1).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 2_000_000_000; // 2 TAO - let max_lending_cap = 100_000_000_000; // 100 TAO - let emissions_share = 10; // 10% - - assert_err!( - SubtensorModule::create_subnet_lending_pool( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - max_lending_cap, - emissions_share - ), - Error::::LendingPoolNotEnoughBalanceToPayInitialDeposit - ); - }); -} diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 65c0a5f5e9..4271bb1c03 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -185,6 +185,8 @@ parameter_types! { pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks + pub const MinCrowdloanBlocksDuration: u64 = 20; // 20 blocks + pub const MinCrowdloanInitialDeposit: u64 = 2_000_000_000; // 2 TAO pub const LendingPoolsLimit: u32 = 5; // 5 lending pools pub const LendingPoolMinInitialDeposit: u64 = 1_000_000_000; // 1 TAO pub const LendingPoolMaxLendingCap: u64 = 1_000_000_000_000; // 1000 TAO @@ -412,6 +414,8 @@ impl crate::Config for Test { type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; + type MinCrowdloanBlocksDuration = MinCrowdloanBlocksDuration; + type MinCrowdloanInitialDeposit = MinCrowdloanInitialDeposit; type LendingPoolsLimit = LendingPoolsLimit; type LendingPoolMinInitialDeposit = LendingPoolMinInitialDeposit; type LendingPoolMaxLendingCap = LendingPoolMaxLendingCap; diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index aa8b22f4b3..8964bffceb 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -5,7 +5,7 @@ mod delegate_info; mod difficulty; mod emission; mod epoch; -mod lending_pools; +mod crowdloan; mod math; mod migration; mod mock; From f123607d75abd3e105bb7595b26a71f9831df7c5 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 31 Mar 2025 11:20:11 +0200 Subject: [PATCH 05/70] wip move specific subnet crowdloan to a more generic crowdloan pallet --- Cargo.lock | 17 ++ pallets/crowdloan/Cargo.toml | 52 +++++ pallets/crowdloan/src/lib.rs | 238 ++++++++++++++++++++++ pallets/crowdloan/src/tests.rs | 353 +++++++++++++++++++++++++++++++++ 4 files changed, 660 insertions(+) create mode 100644 pallets/crowdloan/Cargo.toml create mode 100644 pallets/crowdloan/src/lib.rs create mode 100644 pallets/crowdloan/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 385175fcff..1270d6d10d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6103,6 +6103,23 @@ dependencies = [ "subtensor-macros", ] +[[package]] +name = "pallet-crowdloan" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "subtensor-macros", +] + [[package]] name = "pallet-drand" version = "0.0.1" diff --git a/pallets/crowdloan/Cargo.toml b/pallets/crowdloan/Cargo.toml new file mode 100644 index 0000000000..23dedb9690 --- /dev/null +++ b/pallets/crowdloan/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "pallet-crowdloan" +version = "0.1.0" +edition = "2024" +authors = ["Bittensor Nucleus Team"] +license = "Apache-2.0" +homepage = "https://bittensor.com" +description = "FRAME crowdloan pallet" +publish = false +repository = "https://github.com/opentensor/subtensor" + +[lints] +workspace = true + +[dependencies] +subtensor-macros.workspace = true +scale-info = { workspace = true, features = ["derive"] } +codec = { workspace = true, features = ["max-encoded-len"] } +frame-benchmarking = { optional = true, workspace = true } +frame-support.workspace = true +frame-system.workspace = true +sp-runtime.workspace = true +sp-std.workspace = true + +[dev-dependencies] +pallet-balances = { default-features = true, workspace = true } +sp-core = { default-features = true, workspace = true } +sp-io = { default-features = true, workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "pallet-balances/try-runtime", +] diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs new file mode 100644 index 0000000000..f00e771850 --- /dev/null +++ b/pallets/crowdloan/src/lib.rs @@ -0,0 +1,238 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use frame_support::{ + PalletId, + dispatch::GetDispatchInfo, + sp_runtime::RuntimeDebug, + traits::{Currency, Get, IsSubType, ReservableCurrency, tokens::ExistenceRequirement}, +}; +use scale_info::TypeInfo; + +pub use pallet::*; +use sp_runtime::traits::AccountIdConversion; + +type CrowdloanId = u32; + +mod tests; + +type CurrencyOf = ::Currency; +type BalanceOf = as Currency<::AccountId>>::Balance; + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct CrowdloanInfo { + pub depositor: AccountId, + pub deposit: Balance, + pub minimum_contribution: Balance, + pub end: BlockNumber, + pub cap: Balance, + pub raised: Balance, +} + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*, sp_runtime::traits::Dispatchable}; + use frame_system::pallet_prelude::{BlockNumberFor, *}; + use sp_runtime::traits::CheckedSub; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type RuntimeCall: Parameter + + Dispatchable + + GetDispatchInfo + + From> + + IsSubType> + + IsType<::RuntimeCall>; + + type Currency: ReservableCurrency; + + #[pallet::constant] + type PalletId: Get; + + #[pallet::constant] + type MinimumDeposit: Get>; + + #[pallet::constant] + type AbsoluteMinimumContribution: Get>; + + #[pallet::constant] + type MinimumBlockDuration: Get>; + + #[pallet::constant] + type MaximumBlockDuration: Get>; + } + + #[pallet::storage] + pub type Crowdloans = StorageMap< + _, + Identity, + CrowdloanId, + CrowdloanInfo, BlockNumberFor>, + OptionQuery, + >; + + #[pallet::storage] + pub type NextCrowdloanId = StorageValue<_, CrowdloanId, ValueQuery, ConstU32<0>>; + + #[pallet::storage] + pub type Contributions = StorageDoubleMap< + _, + Identity, + CrowdloanId, + Identity, + T::AccountId, + BalanceOf, + OptionQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Created { + crowdloan_id: CrowdloanId, + depositor: T::AccountId, + end: BlockNumberFor, + cap: BalanceOf, + }, + } + + #[pallet::error] + pub enum Error { + DepositTooLow, + CapTooLow, + MinimumContributionTooLow, + CannotEndInPast, + BlockDurationTooShort, + BlockDurationTooLong, + InsufficientBalance, + Overflow, + } + + #[pallet::call] + impl Pallet { + /// Create a crowdloan + #[pallet::call_index(0)] + pub fn create( + origin: OriginFor, + deposit: BalanceOf, + minimum_contribution: BalanceOf, + cap: BalanceOf, + end: BlockNumberFor, + ) -> DispatchResult { + let depositor = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + + // Ensure the deposit is at least the minimum deposit and cap is greater + // than the deposit + ensure!( + deposit >= T::MinimumDeposit::get(), + Error::::DepositTooLow + ); + ensure!(cap > deposit, Error::::CapTooLow); + + // Ensure the minimum contribution is at least the absolute minimum contribution + ensure!( + minimum_contribution >= T::AbsoluteMinimumContribution::get(), + Error::::MinimumContributionTooLow + ); + + // Ensure the end block is after the current block and the duration is + // between the minimum and maximum block duration + ensure!(end > now, Error::::CannotEndInPast); + let block_duration = end.checked_sub(&now).expect("checked end after now; qed"); + ensure!( + block_duration >= T::MinimumBlockDuration::get(), + Error::::BlockDurationTooShort + ); + ensure!( + block_duration <= T::MaximumBlockDuration::get(), + Error::::BlockDurationTooLong + ); + + // Ensure the depositor has enough balance to pay the deposit. + ensure!( + T::Currency::free_balance(&depositor) >= deposit, + Error::::InsufficientBalance + ); + + let crowdloan_id = NextCrowdloanId::::get(); + let next_crowdloan_id = crowdloan_id.checked_add(1).ok_or(Error::::Overflow)?; + + Crowdloans::::insert( + &crowdloan_id, + CrowdloanInfo { + depositor: depositor.clone(), + deposit, + minimum_contribution, + end, + cap, + raised: deposit, + }, + ); + + NextCrowdloanId::::put(next_crowdloan_id); + + // Transfer the deposit to the crowdloan account + T::Currency::transfer( + &depositor, + &Self::crowdloan_account_id(crowdloan_id), + deposit, + ExistenceRequirement::AllowDeath, + )?; + + // Add initial deposit to contributions + Contributions::::insert(&crowdloan_id, &depositor, deposit); + + Self::deposit_event(Event::::Created { + crowdloan_id, + depositor, + end, + cap, + }); + + Ok(()) + } + + /// Contribute to a crowdloan + #[pallet::call_index(1)] + pub fn contribute(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + /// Withdraw all contributior balance from a crowdloan + #[pallet::call_index(2)] + pub fn withdraw(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + /// Refund every contributor's balance from a crowdloan + #[pallet::call_index(3)] + pub fn refund(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + /// Remove fund after end and refunds + #[pallet::call_index(4)] + pub fn dissolve(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + /// Edit configuration + #[pallet::call_index(5)] + pub fn edit(origin: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +impl Pallet { + pub fn crowdloan_account_id(id: CrowdloanId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating(id) + } +} diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs new file mode 100644 index 0000000000..5dd338756c --- /dev/null +++ b/pallets/crowdloan/src/tests.rs @@ -0,0 +1,353 @@ +#![cfg(test)] + +use frame_support::{PalletId, assert_err, assert_ok, derive_impl, parameter_types}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_core::U256; +use sp_runtime::{BuildStorage, DispatchError, traits::IdentityLookup}; + +use crate::{BalanceOf, CrowdloanInfo, pallet as pallet_crowdloan}; + +type Block = frame_system::mocking::MockBlock; +type AccountOf = ::AccountId; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system = 1, + Balances: pallet_balances = 2, + Crowdloan: pallet_crowdloan = 3, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountId = U256; + type AccountData = pallet_balances::AccountData; + type Lookup = IdentityLookup; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const CrowdloanId: PalletId = PalletId(*b"bt/cloan"); + pub const MinimumDeposit: u64 = 50; + pub const AbsoluteMinimumContribution: u64 = 10; + pub const MinimumBlockDuration: u64 = 20; + pub const MaximumBlockDuration: u64 = 100; +} + +impl pallet_crowdloan::Config for Test { + type PalletId = CrowdloanId; + type Currency = Balances; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MinimumDeposit = MinimumDeposit; + type AbsoluteMinimumContribution = AbsoluteMinimumContribution; + type MinimumBlockDuration = MinimumBlockDuration; + type MaximumBlockDuration = MaximumBlockDuration; +} + +struct TestState { + block_number: BlockNumberFor, + balances: Vec<(AccountOf, BalanceOf)>, +} + +impl Default for TestState { + fn default() -> Self { + Self { + block_number: 1, + balances: vec![], + } + } +} + +impl TestState { + fn with_block_number(mut self, block_number: BlockNumberFor) -> Self { + self.block_number = block_number; + self + } + + fn with_balance(mut self, who: AccountOf, balance: BalanceOf) -> Self { + self.balances.push((who, balance)); + self + } + + fn build_and_execute(self, test: impl FnOnce() -> ()) { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self + .balances + .iter() + .map(|(who, balance)| (*who, *balance)) + .collect::>(), + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(self.block_number)); + ext.execute_with(test); + } +} + +fn last_event() -> RuntimeEvent { + System::events().pop().expect("RuntimeEvent expected").event +} + +#[test] +fn test_create_crowdloan_succeeds() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let who: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(who), + deposit, + minimum_contribution, + cap, + end, + )); + + let crowdloan_id = 0; + // ensure the crowdloan is stored correctly + assert_eq!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id), + Some(CrowdloanInfo { + depositor: who, + deposit, + minimum_contribution, + cap, + end, + raised: deposit, + }) + ); + // ensure the crowdloan account has the deposit + assert_eq!( + Balances::free_balance(&pallet_crowdloan::Pallet::::crowdloan_account_id( + crowdloan_id + )), + deposit + ); + // ensure the contributions has been updated + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, who), + Some(deposit) + ); + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Created { + crowdloan_id, + depositor: who, + end, + cap, + } + .into() + ); + }); +} + +#[test] +fn test_create_crowdloan_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::none(), + deposit, + minimum_contribution, + cap, + end + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_create_crowdloan_fails_if_deposit_is_too_low() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let who: AccountOf = U256::from(1); + let deposit: BalanceOf = 20; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::signed(who), + deposit, + minimum_contribution, + cap, + end + ), + pallet_crowdloan::Error::::DepositTooLow + ); + }); +} + +#[test] +fn test_create_crowdloan_fails_if_cap_is_not_greater_than_deposit() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let who: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 40; + let end: BlockNumberFor = 50; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::signed(who), + deposit, + minimum_contribution, + cap, + end + ), + pallet_crowdloan::Error::::CapTooLow + ); + }); +} + +#[test] +fn test_create_crowdloan_fails_if_minimum_contribution_is_too_low() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let who: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 5; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::signed(who), + deposit, + minimum_contribution, + cap, + end + ), + pallet_crowdloan::Error::::MinimumContributionTooLow + ); + }); +} + +#[test] +fn test_create_crowdloan_fails_if_end_is_in_the_past() { + let current_block_number: BlockNumberFor = 10; + + TestState::default() + .with_block_number(current_block_number) + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let who: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = current_block_number - 5; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::signed(who), + deposit, + minimum_contribution, + cap, + end + ), + pallet_crowdloan::Error::::CannotEndInPast + ); + }); +} + +#[test] +fn test_create_crowdloan_fails_if_block_duration_is_too_short() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let who: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 11; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::signed(who), + deposit, + minimum_contribution, + cap, + end + ), + pallet_crowdloan::Error::::BlockDurationTooShort + ); + }); +} + +#[test] +fn test_create_crowdloan_fails_if_block_duration_is_too_long() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let who: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 1000; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::signed(who), + deposit, + minimum_contribution, + cap, + end + ), + pallet_crowdloan::Error::::BlockDurationTooLong + ); + }); +} + +#[test] +fn test_create_crowdloan_fails_if_depositor_has_insufficient_balance() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let who: AccountOf = U256::from(1); + let deposit: BalanceOf = 200; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::signed(who), + deposit, + minimum_contribution, + cap, + end + ), + pallet_crowdloan::Error::::InsufficientBalance + ); + }); +} From 5f340180c75e3473c0ac55c8cc1429ca0302f159 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 31 Mar 2025 11:38:58 +0200 Subject: [PATCH 06/70] track crowdloan account id --- pallets/crowdloan/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index f00e771850..c7b385722b 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -179,6 +179,7 @@ pub mod pallet { NextCrowdloanId::::put(next_crowdloan_id); // Transfer the deposit to the crowdloan account + frame_system::Pallet::::inc_providers(&Self::crowdloan_account_id(crowdloan_id)); T::Currency::transfer( &depositor, &Self::crowdloan_account_id(crowdloan_id), From a70a1d03f99b0583186257342041c309fed07ce1 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 31 Mar 2025 13:29:50 +0200 Subject: [PATCH 07/70] contribute to crowdloan + tests --- pallets/crowdloan/src/lib.rs | 87 +++++++-- pallets/crowdloan/src/tests.rs | 340 +++++++++++++++++++++++++++++---- 2 files changed, 379 insertions(+), 48 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index c7b385722b..5dd5d412a8 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -10,9 +10,9 @@ use frame_support::{ use scale_info::TypeInfo; pub use pallet::*; -use sp_runtime::traits::AccountIdConversion; +use sp_runtime::traits::{AccountIdConversion, CheckedAdd, Zero}; -type CrowdloanId = u32; +type CrowdloanIndex = u32; mod tests; @@ -72,19 +72,19 @@ pub mod pallet { pub type Crowdloans = StorageMap< _, Identity, - CrowdloanId, + CrowdloanIndex, CrowdloanInfo, BlockNumberFor>, OptionQuery, >; #[pallet::storage] - pub type NextCrowdloanId = StorageValue<_, CrowdloanId, ValueQuery, ConstU32<0>>; + pub type NextCrowdloanIndex = StorageValue<_, CrowdloanIndex, ValueQuery, ConstU32<0>>; #[pallet::storage] pub type Contributions = StorageDoubleMap< _, Identity, - CrowdloanId, + CrowdloanIndex, Identity, T::AccountId, BalanceOf, @@ -95,11 +95,16 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { Created { - crowdloan_id: CrowdloanId, + crowdloan_id: CrowdloanIndex, depositor: T::AccountId, end: BlockNumberFor, cap: BalanceOf, }, + Contributed { + crowdloan_id: CrowdloanIndex, + contributor: T::AccountId, + amount: BalanceOf, + }, } #[pallet::error] @@ -112,6 +117,11 @@ pub mod pallet { BlockDurationTooLong, InsufficientBalance, Overflow, + InvalidCrowdloanIndex, + CapRaised, + CapExceeded, + ContributionPeriodEnded, + ContributionTooLow, } #[pallet::call] @@ -120,13 +130,12 @@ pub mod pallet { #[pallet::call_index(0)] pub fn create( origin: OriginFor, - deposit: BalanceOf, - minimum_contribution: BalanceOf, - cap: BalanceOf, - end: BlockNumberFor, + #[pallet::compact] deposit: BalanceOf, + #[pallet::compact] minimum_contribution: BalanceOf, + #[pallet::compact] cap: BalanceOf, + #[pallet::compact] end: BlockNumberFor, ) -> DispatchResult { let depositor = ensure_signed(origin)?; - let now = frame_system::Pallet::::block_number(); // Ensure the deposit is at least the minimum deposit and cap is greater // than the deposit @@ -144,6 +153,7 @@ pub mod pallet { // Ensure the end block is after the current block and the duration is // between the minimum and maximum block duration + let now = frame_system::Pallet::::block_number(); ensure!(end > now, Error::::CannotEndInPast); let block_duration = end.checked_sub(&now).expect("checked end after now; qed"); ensure!( @@ -161,7 +171,7 @@ pub mod pallet { Error::::InsufficientBalance ); - let crowdloan_id = NextCrowdloanId::::get(); + let crowdloan_id = NextCrowdloanIndex::::get(); let next_crowdloan_id = crowdloan_id.checked_add(1).ok_or(Error::::Overflow)?; Crowdloans::::insert( @@ -176,7 +186,7 @@ pub mod pallet { }, ); - NextCrowdloanId::::put(next_crowdloan_id); + NextCrowdloanIndex::::put(next_crowdloan_id); // Transfer the deposit to the crowdloan account frame_system::Pallet::::inc_providers(&Self::crowdloan_account_id(crowdloan_id)); @@ -196,13 +206,58 @@ pub mod pallet { end, cap, }); - Ok(()) } /// Contribute to a crowdloan #[pallet::call_index(1)] - pub fn contribute(origin: OriginFor) -> DispatchResult { + pub fn contribute( + origin: OriginFor, + #[pallet::compact] crowdloan_id: CrowdloanIndex, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + let contributor = ensure_signed(origin)?; + + // Ensure the crowdloan exists + let mut crowdloan = + Crowdloans::::get(crowdloan_id).ok_or(Error::::InvalidCrowdloanIndex)?; + + // Ensure the crowdloan has not ended + let now = frame_system::Pallet::::block_number(); + ensure!(crowdloan.end > now, Error::::ContributionPeriodEnded); + + // Ensure the cap has not been fully raised + ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); + + // Ensure the contribution is at least the minimum contribution + ensure!( + amount >= crowdloan.minimum_contribution, + Error::::ContributionTooLow + ); + + // Ensure the contribution does not overflow the actual raised amount + // and it does not exceed the cap + crowdloan.raised = crowdloan + .raised + .checked_add(&amount) + .ok_or(Error::::Overflow)?; + ensure!(crowdloan.raised <= crowdloan.cap, Error::::CapExceeded); + + // Ensure the contribution does not overflow the contributor's balance and update + // the contribution + let contribution = Contributions::::get(&crowdloan_id, &contributor) + .unwrap_or(Zero::zero()) + .checked_add(&amount) + .ok_or(Error::::Overflow)?; + Contributions::::insert(&crowdloan_id, &contributor, contribution); + + Crowdloans::::insert(crowdloan_id, &crowdloan); + + Self::deposit_event(Event::::Contributed { + contributor, + crowdloan_id, + amount, + }); Ok(()) } @@ -233,7 +288,7 @@ pub mod pallet { } impl Pallet { - pub fn crowdloan_account_id(id: CrowdloanId) -> T::AccountId { + pub fn crowdloan_account_id(id: CrowdloanIndex) -> T::AccountId { T::PalletId::get().into_sub_account_truncating(id) } } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 5dd338756c..3138758082 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1,11 +1,14 @@ #![cfg(test)] -use frame_support::{PalletId, assert_err, assert_ok, derive_impl, parameter_types}; +use frame_support::{ + PalletId, assert_err, assert_ok, derive_impl, parameter_types, + traits::{OnFinalize, OnInitialize}, +}; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::U256; use sp_runtime::{BuildStorage, DispatchError, traits::IdentityLookup}; -use crate::{BalanceOf, CrowdloanInfo, pallet as pallet_crowdloan}; +use crate::{BalanceOf, CrowdloanIndex, CrowdloanInfo, pallet as pallet_crowdloan}; type Block = frame_system::mocking::MockBlock; type AccountOf = ::AccountId; @@ -51,7 +54,7 @@ impl pallet_crowdloan::Config for Test { type MaximumBlockDuration = MaximumBlockDuration; } -struct TestState { +pub(crate) struct TestState { block_number: BlockNumberFor, balances: Vec<(AccountOf, BalanceOf)>, } @@ -98,23 +101,34 @@ impl TestState { } } -fn last_event() -> RuntimeEvent { +pub(crate) fn last_event() -> RuntimeEvent { System::events().pop().expect("RuntimeEvent expected").event } +pub(crate) fn run_to_block(n: u64) { + while System::block_number() < n { + System::on_finalize(System::block_number()); + Balances::on_finalize(System::block_number()); + System::reset_events(); + System::set_block_number(System::block_number() + 1); + Balances::on_initialize(System::block_number()); + System::on_initialize(System::block_number()); + } +} + #[test] -fn test_create_crowdloan_succeeds() { +fn test_create_succeeds() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let who: AccountOf = U256::from(1); + let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(depositor), deposit, minimum_contribution, cap, @@ -126,7 +140,7 @@ fn test_create_crowdloan_succeeds() { assert_eq!( pallet_crowdloan::Crowdloans::::get(crowdloan_id), Some(CrowdloanInfo { - depositor: who, + depositor, deposit, minimum_contribution, cap, @@ -143,7 +157,7 @@ fn test_create_crowdloan_succeeds() { ); // ensure the contributions has been updated assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, who), + pallet_crowdloan::Contributions::::get(crowdloan_id, depositor), Some(deposit) ); // ensure the event is emitted @@ -151,7 +165,7 @@ fn test_create_crowdloan_succeeds() { last_event(), pallet_crowdloan::Event::::Created { crowdloan_id, - depositor: who, + depositor, end, cap, } @@ -161,7 +175,7 @@ fn test_create_crowdloan_succeeds() { } #[test] -fn test_create_crowdloan_fails_if_bad_origin() { +fn test_create_fails_if_bad_origin() { TestState::default().build_and_execute(|| { let deposit: BalanceOf = 50; let minimum_contribution: BalanceOf = 10; @@ -182,11 +196,11 @@ fn test_create_crowdloan_fails_if_bad_origin() { } #[test] -fn test_create_crowdloan_fails_if_deposit_is_too_low() { +fn test_create_fails_if_deposit_is_too_low() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let who: AccountOf = U256::from(1); + let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 20; let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; @@ -194,7 +208,7 @@ fn test_create_crowdloan_fails_if_deposit_is_too_low() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(depositor), deposit, minimum_contribution, cap, @@ -206,11 +220,11 @@ fn test_create_crowdloan_fails_if_deposit_is_too_low() { } #[test] -fn test_create_crowdloan_fails_if_cap_is_not_greater_than_deposit() { +fn test_create_fails_if_cap_is_not_greater_than_deposit() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let who: AccountOf = U256::from(1); + let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 40; @@ -218,7 +232,7 @@ fn test_create_crowdloan_fails_if_cap_is_not_greater_than_deposit() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(depositor), deposit, minimum_contribution, cap, @@ -230,11 +244,11 @@ fn test_create_crowdloan_fails_if_cap_is_not_greater_than_deposit() { } #[test] -fn test_create_crowdloan_fails_if_minimum_contribution_is_too_low() { +fn test_create_fails_if_minimum_contribution_is_too_low() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let who: AccountOf = U256::from(1); + let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let minimum_contribution: BalanceOf = 5; let cap: BalanceOf = 300; @@ -242,7 +256,7 @@ fn test_create_crowdloan_fails_if_minimum_contribution_is_too_low() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(depositor), deposit, minimum_contribution, cap, @@ -254,14 +268,14 @@ fn test_create_crowdloan_fails_if_minimum_contribution_is_too_low() { } #[test] -fn test_create_crowdloan_fails_if_end_is_in_the_past() { +fn test_create_fails_if_end_is_in_the_past() { let current_block_number: BlockNumberFor = 10; TestState::default() .with_block_number(current_block_number) .with_balance(U256::from(1), 100) .build_and_execute(|| { - let who: AccountOf = U256::from(1); + let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; @@ -269,7 +283,7 @@ fn test_create_crowdloan_fails_if_end_is_in_the_past() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(depositor), deposit, minimum_contribution, cap, @@ -281,11 +295,11 @@ fn test_create_crowdloan_fails_if_end_is_in_the_past() { } #[test] -fn test_create_crowdloan_fails_if_block_duration_is_too_short() { +fn test_create_fails_if_block_duration_is_too_short() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let who: AccountOf = U256::from(1); + let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; @@ -293,7 +307,7 @@ fn test_create_crowdloan_fails_if_block_duration_is_too_short() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(depositor), deposit, minimum_contribution, cap, @@ -305,11 +319,11 @@ fn test_create_crowdloan_fails_if_block_duration_is_too_short() { } #[test] -fn test_create_crowdloan_fails_if_block_duration_is_too_long() { +fn test_create_fails_if_block_duration_is_too_long() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let who: AccountOf = U256::from(1); + let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; @@ -317,7 +331,7 @@ fn test_create_crowdloan_fails_if_block_duration_is_too_long() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(depositor), deposit, minimum_contribution, cap, @@ -329,11 +343,11 @@ fn test_create_crowdloan_fails_if_block_duration_is_too_long() { } #[test] -fn test_create_crowdloan_fails_if_depositor_has_insufficient_balance() { +fn test_create_fails_if_depositor_has_insufficient_balance() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let who: AccountOf = U256::from(1); + let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 200; let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; @@ -341,7 +355,7 @@ fn test_create_crowdloan_fails_if_depositor_has_insufficient_balance() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(who), + RuntimeOrigin::signed(depositor), deposit, minimum_contribution, cap, @@ -351,3 +365,265 @@ fn test_create_crowdloan_fails_if_depositor_has_insufficient_balance() { ); }); } + +#[test] +fn test_contribute_succeeds() { + TestState::default() + .with_balance(U256::from(1), 200) + .with_balance(U256::from(2), 500) + .with_balance(U256::from(3), 200) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // first contribution to the crowdloan from depositor + let crowdloan_id: CrowdloanIndex = 0; + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(depositor), + crowdloan_id, + amount + )); + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Contributed { + crowdloan_id, + contributor: depositor, + amount, + } + .into() + ); + + // second contribution to the crowdloan + let contributor1: AccountOf = U256::from(2); + let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor1), + crowdloan_id, + amount + )); + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Contributed { + crowdloan_id, + contributor: contributor1, + amount, + } + .into() + ); + + // third contribution to the crowdloan + let contributor2: AccountOf = U256::from(3); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor2), + crowdloan_id, + amount + )); + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Contributed { + crowdloan_id, + contributor: contributor2, + amount, + } + .into() + ); + + // ensure the contributions are updated correctly + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, depositor), + Some(100) + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, contributor1), + Some(100) + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, contributor2), + Some(50) + ); + + // ensure the crowdloan raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 250) + ); + }); +} + +#[test] +fn test_contribute_fails_if_crowdloan_does_not_exist() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let contributor: AccountOf = U256::from(1); + let crowdloan_id: CrowdloanIndex = 0; + let amount: BalanceOf = 20; + + assert_err!( + Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), + pallet_crowdloan::Error::::InvalidCrowdloanIndex + ); + }); +} + +#[test] +fn test_contribute_fails_if_crowdloan_has_ended() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run past the end of the crowdloan + run_to_block(60); + + // contribute to the crowdloan + let contributor: AccountOf = U256::from(2); + let crowdloan_id: CrowdloanIndex = 0; + let amount: BalanceOf = 20; + assert_err!( + Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), + pallet_crowdloan::Error::::ContributionPeriodEnded + ); + }); +} + +#[test] +fn test_contribute_fails_if_cap_has_been_raised() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 1000) + .with_balance(U256::from(3), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // first contribution to the crowdloan fully raise the cap + let crowdloan_id: CrowdloanIndex = 0; + let contributor1: AccountOf = U256::from(2); + let amount: BalanceOf = cap - initial_deposit; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor1), + crowdloan_id, + amount + )); + + // second contribution to the crowdloan + let contributor2: AccountOf = U256::from(3); + let amount: BalanceOf = 10; + assert_err!( + Crowdloan::contribute(RuntimeOrigin::signed(contributor2), crowdloan_id, amount), + pallet_crowdloan::Error::::CapRaised + ); + }); +} + +#[test] +fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // contribute to the crowdloan + let contributor: AccountOf = U256::from(2); + let crowdloan_id: CrowdloanIndex = 0; + let amount: BalanceOf = 5; + assert_err!( + Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), + pallet_crowdloan::Error::::ContributionTooLow + ) + }); +} + +#[test] +fn test_contribute_fails_if_contribution_will_make_the_raised_amount_exceed_the_cap() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 1000) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // contribute to the crowdloan + let contributor: AccountOf = U256::from(2); + let crowdloan_id: CrowdloanIndex = 0; + let amount: BalanceOf = 300; + assert_err!( + Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), + pallet_crowdloan::Error::::CapExceeded + ); + }); +} From 8f3b883ee75c006b83acfe30bdd93f9bded9824c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 31 Mar 2025 14:27:13 +0200 Subject: [PATCH 08/70] cleanup crowdloan from subtensor pallet --- pallets/admin-utils/src/tests/mock.rs | 4 - pallets/subtensor/src/crowdloan/mod.rs | 190 ------------- pallets/subtensor/src/lib.rs | 44 +-- pallets/subtensor/src/macros/config.rs | 18 -- pallets/subtensor/src/macros/dispatches.rs | 15 +- pallets/subtensor/src/macros/errors.rs | 18 -- pallets/subtensor/src/tests/crowdloan.rs | 314 --------------------- pallets/subtensor/src/tests/mock.rs | 12 - pallets/subtensor/src/tests/mod.rs | 1 - runtime/src/lib.rs | 8 - 10 files changed, 2 insertions(+), 622 deletions(-) delete mode 100644 pallets/subtensor/src/crowdloan/mod.rs delete mode 100644 pallets/subtensor/src/tests/crowdloan.rs diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 096e8d63c0..fc0d016198 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -199,10 +199,6 @@ impl pallet_subtensor::Config for Test { type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; - type LendingPoolsLimit = (); - type LendingPoolMinInitialDeposit = (); - type LendingPoolMaxLendingCap = (); - type LendingPoolMinEmissionsShare = (); } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/pallets/subtensor/src/crowdloan/mod.rs b/pallets/subtensor/src/crowdloan/mod.rs deleted file mode 100644 index 59e29cecb0..0000000000 --- a/pallets/subtensor/src/crowdloan/mod.rs +++ /dev/null @@ -1,190 +0,0 @@ -// This file is heavily inspired by Polkadot's Crowdloan implementation: -// https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/runtime/common/src/crowdloan/mod.rs - -use super::*; -use frame_system::pallet_prelude::BlockNumberFor; -use sp_io::hashing::blake2_256; -use sp_runtime::{ - Percent, - traits::{Saturating, TrailingZeroInput}, -}; - -impl Pallet { - pub fn do_create_subnet_crowdloan( - origin: T::RuntimeOrigin, - initial_deposit: u64, - cap: u64, - emissions_share: Percent, - end: BlockNumberFor, - ) -> dispatch::DispatchResult { - let creator = ensure_signed(origin)?; - let now = frame_system::Pallet::::block_number(); - - // Ensure crowdloan cannot end in the past. - ensure!(end > now, Error::::CrowdloanCannotEndInPast); - - // Ensure crowdloan duration is at least the minimum required. - let duration = end.saturating_sub(now); - ensure!( - duration > T::MinCrowdloanBlocksDuration::get(), - Error::::CrowdloanBlocksDurationTooShort - ); - - // Ensure the initial deposit is at least the minimum required. - ensure!( - initial_deposit >= T::MinCrowdloanInitialDeposit::get(), - Error::::CrowdloanInitialDepositTooLow - ); - - // Ensure the cap is more than the initial deposit. - ensure!( - cap > initial_deposit, - Error::::CrowdloanCapInferiorToInitialDeposit - ); - - let crowdloan_index = NextSubnetCrowdloanIndex::::get(); - - // let new_crowdfund_index = crowdloan_index.checked_add(1).ok_or(Error::::Overflow)?; - - // An existing crowdloan with the same index should not exist. - // ensure!( - // !SubnetCrowdloans::::contains_key(crowdloan_index), - // Error::::CrowdloanIndexTaken, - // ); - - // // Ensure we have reached the maximum number of lending pools - // ensure!( - // NextLendingPoolId::::get() < T::LendingPoolsLimit::get(), - // Error::::LendingPoolsLimitReached - // ); - // // Ensure the initial deposit is above the minimum required to create a lending pool. - // ensure!( - // initial_deposit >= T::LendingPoolMinInitialDeposit::get(), - // Error::::LendingPoolInitialDepositTooLow - // ); - // // Ensure the max lending cap is at least superior to the initial deposit. - // ensure!( - // max_lending_cap > initial_deposit, - // Error::::LendingPoolLendingCapInferiorToInitialDeposit - // ); - // // Ensure the max lending cap is not greater than the maximum allowed. - // ensure!( - // max_lending_cap <= T::LendingPoolMaxLendingCap::get(), - // Error::::LendingPoolLendingCapTooHigh - // ); - // // Ensure the emisions share is at a minimum of some value. - // ensure!( - // emissions_share >= T::LendingPoolMinEmissionsShare::get(), - // Error::::LendingPoolEmissionsShareTooLow - // ); - // // Ensure the emissions share is not greater than 100%. - // ensure!( - // emissions_share <= 100, - // Error::::LendingPoolEmissionsShareTooHigh - // ); - // // Ensure creator coldkey contains the initial deposit. - // ensure!( - // Self::get_coldkey_balance(&creator_coldkey) >= initial_deposit, - // Error::::LendingPoolNotEnoughBalanceToPayInitialDeposit - // ); - - // // Get the next pool id and increment it. - // let pool_id = NextLendingPoolId::::get(); - // NextLendingPoolId::::mutate(|id| *id = id.saturating_add(1)); - - // // Derive the pool coldkey and hotkey. - // let pool_coldkey = Self::get_lending_pool_coldkey(pool_id); - // let _pool_hotkey = Self::get_lending_pool_hotkey(pool_id); - - // LendingPools::::insert( - // pool_id, - // LendingPool { - // creator: creator_coldkey.clone(), - // initial_deposit, - // cap, - // emissions_share, - // }, - // ); - - // // Transfer the initial deposit from the creator coldkey to the pool coldkey. - // T::Currency::transfer( - // &creator_coldkey, - // &pool_coldkey, - // initial_deposit, - // Preservation::Expendable, - // )?; - - // // Add initial deposit to individual pool contributions. - // LendingPoolIndividualContributions::::mutate(pool_id, creator_coldkey, |contribution| { - // *contribution = contribution.saturating_add(initial_deposit); - // }); - // // Add initial deposit to total pool contributions. - // LendingPoolTotalContributions::::mutate(pool_id, |total| { - // *total = total.saturating_add(initial_deposit); - // }); - - Ok(()) - } - - // pub fn do_contribute_to_subnet_crowdloan( - // origin: T::RuntimeOrigin, - // pool_id: u32, - // amount: u64, - // ) -> dispatch::DispatchResult { - // let contributor_coldkey = ensure_signed(origin)?; - - // let lending_pool = - // LendingPools::::get(pool_id).ok_or(Error::::LendingPoolDoesNotExist)?; - - // // Ensure the contributor has enough balance to contribute. - // ensure!( - // Self::get_coldkey_balance(&contributor_coldkey) >= amount, - // Error::::NotEnoughBalanceToContributeToLendingPool - // ); - - // // Ensure the lending pool has not reached its max lending cap. - // let total_contributions = LendingPoolTotalContributions::::get(pool_id); - - // Ok(()) - // } - - pub fn get_crowdloan_coldkey(crowdloan_index: u32) -> T::AccountId { - let entropy = (b"subtensor/crowdloan/cold/", crowdloan_index).using_encoded(blake2_256); - let key = T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed"); - - key - } - - pub fn get_crowdloan_hotkey(crowdloan_index: u32) -> T::AccountId { - let entropy = (b"subtensor/crowdloan/hot/", crowdloan_index).using_encoded(blake2_256); - let key = T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed"); - - key - } -} - -// fn edit_lending_proposal_cut() {} - -// fn edit_lending_proposal_cap() {} - -// fn edit_lending_proposal_end() {} - -// maximum of pools for a specific user? -// // - minimum contribution bound -// // - if not already contributed, add as lender -// // - if already contributed, add to lending amount -// fn participate_to_lending_proposal(origin: (), cut: (), cap: (), end: ()) {} - -// // The owner of the proposal can call this extrinsic to finalize the -// // proposal, it will be checked if the pooled fund are enough to register -// // for a subnet, then it will register the subnet -// fn finalize_lending_proposal() {} - -// // When emission are received by the lend pool, distribute the cut of the subnet owner -// // to the lenders by sending the alpha to the ema price so lenders receive TAO. -// fn hook_on_emission() {} - -// // When on lend end, transfer ownership of the subnet to the subnet operator. -// fn hook_on_lease_end() {} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d54e3c2dae..eb381b6807 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -38,7 +38,6 @@ mod benchmarks; // ==== Pallet Imports ===== // ========================= pub mod coinbase; -pub mod crowdloan; pub mod epoch; pub mod macros; pub mod migrations; @@ -78,7 +77,7 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use pallet_drand::types::RoundNumber; - use sp_core::{ConstU32, ConstU64, H256}; + use sp_core::{ConstU32, H256}; use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; use sp_std::collections::vec_deque::VecDeque; use sp_std::vec; @@ -270,20 +269,6 @@ pub mod pallet { pub additional: Vec, } - /// Data structure for a subnet lending pool - // #[crate::freeze_struct("27945e6b9277e22b")] - #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] - pub struct LendingPool { - /// The creator of the pool and future owner of the subnet - pub creator: AccountId, - /// Initial deposit from the creator - pub initial_deposit: u64, - /// Hard cap for the fundraising - pub max_lending_cap: u64, - /// Share of future subnet emissions distributed to lenders - pub emissions_share: u64, - } - /// ============================ /// ==== Staking + Accounts ==== /// ============================ @@ -1562,33 +1547,6 @@ pub mod pallet { OptionQuery, >; - /// ===================================== - /// ==== Subnet Crowdloan Storage ==== - /// ===================================== - /// - /// All subnet crowdloans. - /// MAP (crowdloan_id) -> The crowdloan with the given crowdloan_id. - #[pallet::storage] - pub type SubnetCrowdloans = - StorageMap<_, Identity, u32, LendingPool, OptionQuery>; - /// - /// Next subnet crowdloan id. - /// ITEM(pool_id) - #[pallet::storage] - pub type NextSubnetCrowdloanIndex = StorageValue<_, u32, ValueQuery, ConstU32<0>>; - - /// Individual contributions to each lending pool. - /// MAP (pool_id, contributor_coldkey) -> u64 - #[pallet::storage] - pub type LendingPoolIndividualContributions = - StorageDoubleMap<_, Identity, u32, Identity, T::AccountId, u64, ValueQuery, ConstU64<0>>; - - /// Total contributions to each lending pool. - /// MAP (pool_id) -> u64 - #[pallet::storage] - pub type LendingPoolTotalContributions = - StorageMap<_, Identity, u32, u64, ValueQuery, ConstU64<0>>; - /// ================== /// ==== Genesis ===== /// ================== diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index ceddafc19a..18acc4577a 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -211,23 +211,5 @@ mod config { /// Initial EMA price halving period #[pallet::constant] type InitialEmaPriceHalvingPeriod: Get; - /// Minimum crowdloan blocks duration - #[pallet::constant] - type MinCrowdloanBlocksDuration: Get>; - /// Minimum initial deposit for a crowdloan - #[pallet::constant] - type MinCrowdloanInitialDeposit: Get; - /// Maximum number of lending pools - #[pallet::constant] - type LendingPoolsLimit: Get; - /// Minimum initial deposit for a lending pool - #[pallet::constant] - type LendingPoolMinInitialDeposit: Get; - /// Maximum funding cap for a lending pool - #[pallet::constant] - type LendingPoolMaxLendingCap: Get; - /// Minimum emissions share for a lending pool - #[pallet::constant] - type LendingPoolMinEmissionsShare: Get; } } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 04340b58a2..bcd2bb33f5 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -9,7 +9,7 @@ mod dispatches { use frame_support::traits::schedule::DispatchTime; use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_system::pallet_prelude::BlockNumberFor; - use sp_runtime::{Percent, traits::Saturating}; + use sp_runtime::traits::Saturating; use crate::MAX_CRV3_COMMIT_SIZE_BYTES; /// Dispatchable functions allow users to interact with the pallet and invoke state changes. @@ -1909,18 +1909,5 @@ mod dispatches { Ok(()) } - - /// Create a new crowdloan for a subnet. - #[pallet::call_index(92)] - #[pallet::weight(0)] - pub fn create_subnet_crowdloan( - origin: OriginFor, - initial_deposit: u64, - cap: u64, - emissions_share: Percent, - end: BlockNumberFor, - ) -> DispatchResult { - Self::do_create_subnet_crowdloan(origin, initial_deposit, cap, emissions_share, end) - } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 947d2967fa..1f189cd2f6 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -195,23 +195,5 @@ mod errors { ActivityCutoffTooLow, /// Call is disabled CallDisabled, - /// Crowdloan cannot end in the past. - CrowdloanCannotEndInPast, - /// Crowdloan blocks duration is too short. - CrowdloanBlocksDurationTooShort, - /// Crowdloan initial deposit is too low. - CrowdloanInitialDepositTooLow, - /// Crowdloan cap is inferior to initial deposit. - CrowdloanCapInferiorToInitialDeposit, - /// Lending pool emissions share is too low. - LendingPoolEmissionsShareTooLow, - /// Lending pool emissions share is too high. - LendingPoolEmissionsShareTooHigh, - /// Lending pool creator coldkey does not have enough balance to pay initial deposit. - LendingPoolNotEnoughBalanceToPayInitialDeposit, - /// Lending pool does not exist. - LendingPoolDoesNotExist, - /// Not enough balance to contribute to lending pool. - NotEnoughBalanceToContributeToLendingPool, } } diff --git a/pallets/subtensor/src/tests/crowdloan.rs b/pallets/subtensor/src/tests/crowdloan.rs deleted file mode 100644 index 509c225569..0000000000 --- a/pallets/subtensor/src/tests/crowdloan.rs +++ /dev/null @@ -1,314 +0,0 @@ -use crate::*; -use frame_support::{assert_err, assert_ok, traits::Currency}; -use frame_system::RawOrigin; -use sp_core::U256; -use sp_runtime::Percent; - -use super::mock::*; - -// #[test] -// fn test_create_subnet_lending_pool_successfully() { -// new_test_ext(1).execute_with(|| { -// let creator_coldkey = U256::from(1); -// let initial_balance = 5_000_000_000; // 5 TAO -// let initial_deposit = 2_000_000_000; // 2 TAO -// let cap = 100_000_000_000; // 100 TAO -// let emissions_share = Percent::from_percent(10); -// let end: BlockNumber = 100; - -// SubtensorModule::add_balance_to_coldkey_account(&creator_coldkey, initial_balance); - -// assert_ok!(SubtensorModule::create_subnet_crowdloan( -// RuntimeOrigin::signed(creator_coldkey), -// initial_deposit, -// cap, -// emissions_share, -// end -// )); - -// // // Check that the pool was created successfully. -// // assert_eq!( -// // LendingPools::::get(0), -// // Some(LendingPool { -// // creator: creator_coldkey, -// // initial_deposit, -// // max_lending_cap, -// // emissions_share, -// // }) -// // ); -// // // Check that the creator coldkey was debited the initial deposit. -// // assert_eq!( -// // SubtensorModule::get_coldkey_balance(&creator_coldkey), -// // initial_balance - initial_deposit -// // ); -// // // Check that the pool coldkey was credited the initial deposit. -// // let pool_id = 0; // the first pool to be created has id 0 -// // let pool_coldkey = SubtensorModule::get_lending_pool_coldkey(pool_id); -// // assert_eq!( -// // SubtensorModule::get_coldkey_balance(&pool_coldkey), -// // initial_deposit -// // ); -// // // Check that the initial deposit was added to the individual contributions. -// // assert_eq!( -// // LendingPoolIndividualContributions::::get(pool_id, creator_coldkey), -// // initial_deposit -// // ); -// // // Check that the total contributions to the pool are equal to the initial deposit. -// // assert_eq!( -// // LendingPoolTotalContributions::::get(pool_id), -// // initial_deposit -// // ); -// }); -// } - -#[test] -fn test_create_subnet_crowdloan_fails_if_bad_origin() { - new_test_ext(1).execute_with(|| { - let initial_deposit = 2_000_000_000; // 2 TAO - let cap = 100_000_000_000; // 100 TAO - let emissions_share = Percent::from_percent(10); - let end = frame_system::Pallet::::block_number() + 50; // 50 blocks from now - - assert_err!( - SubtensorModule::create_subnet_crowdloan( - RawOrigin::None.into(), - initial_deposit, - cap, - emissions_share, - end - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn test_create_subnet_crowdloan_fails_if_end_is_in_the_past() { - new_test_ext(10).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 2_000_000_000; // 2 TAO - let cap = 100_000_000_000; // 100 TAO - let emissions_share = Percent::from_percent(10); - let end = frame_system::Pallet::::block_number() - 1; // 1 block in the past - - assert_err!( - SubtensorModule::create_subnet_crowdloan( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - cap, - emissions_share, - end - ), - Error::::CrowdloanCannotEndInPast - ) - }); -} - -#[test] -fn test_create_subnet_crowdloan_fails_if_duration_is_too_short() { - new_test_ext(10).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 2_000_000_000; // 2 TAO - let cap = 100_000_000_000; // 100 TAO - let emissions_share = Percent::from_percent(10); - let end = frame_system::Pallet::::block_number() + 5; // 5 blocks from now - - assert_err!( - SubtensorModule::create_subnet_crowdloan( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - cap, - emissions_share, - end - ), - Error::::CrowdloanBlocksDurationTooShort - ); - }); -} - -#[test] -fn test_create_subnet_crowdloan_fails_if_initial_deposit_is_too_low() { - new_test_ext(10).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 1_000_000_000; // 1 TAO - let cap = 100_000_000_000; // 100 TAO - let emissions_share = Percent::from_percent(10); - let end = frame_system::Pallet::::block_number() + 50; // 50 blocks from now - - assert_err!( - SubtensorModule::create_subnet_crowdloan( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - cap, - emissions_share, - end - ), - Error::::CrowdloanInitialDepositTooLow - ); - }) -} - -#[test] -fn test_create_subnet_crowdloan_fails_if_cap_is_inferior_to_initial_deposit() { - new_test_ext(10).execute_with(|| { - let creator_coldkey = U256::from(1); - let initial_deposit = 5_000_000_000; // 5 TAO - let cap = 4_000_000_000; // 4 TAO - let emissions_share = Percent::from_percent(10); - let end = frame_system::Pallet::::block_number() + 50; // 50 blocks from now - - assert_err!( - SubtensorModule::create_subnet_crowdloan( - RuntimeOrigin::signed(creator_coldkey), - initial_deposit, - cap, - emissions_share, - end - ), - Error::::CrowdloanCapInferiorToInitialDeposit - ); - }) -} - -// #[test] -// fn test_create_subnet_lending_pool_fails_if_pool_limit_reached() { -// new_test_ext(1).execute_with(|| { -// let creator_coldkey = U256::from(1); -// let initial_deposit = 2_000_000_000; // 2 TAO -// let max_lending_cap = 100_000_000_000; // 100 TAO -// let emissions_share = 10; // 10% - -// // Simulate the fact that we have reached the maximum number of lending pools. -// NextLendingPoolId::::set(5); - -// assert_err!( -// SubtensorModule::create_subnet_lending_pool( -// RuntimeOrigin::signed(creator_coldkey), -// initial_deposit, -// max_lending_cap, -// emissions_share -// ), -// Error::::LendingPoolsLimitReached -// ); -// }); -// } - -// #[test] -// fn test_create_subnet_lending_pool_fails_if_initial_deposit_too_low() { -// new_test_ext(1).execute_with(|| { -// let creator_coldkey = U256::from(1); -// let initial_deposit = 500_000_000; // 0.5 TAO -// let max_lending_cap = 100_000_000_000; // 100 TAO -// let emissions_share = 10; // 10% - -// assert_err!( -// SubtensorModule::create_subnet_lending_pool( -// RuntimeOrigin::signed(creator_coldkey), -// initial_deposit, -// max_lending_cap, -// emissions_share -// ), -// Error::::LendingPoolInitialDepositTooLow -// ); -// }); -// } - -// #[test] -// fn test_create_subnet_lending_pool_fails_if_lending_cap_inferior_to_initial_deposit() { -// new_test_ext(1).execute_with(|| { -// let creator_coldkey = U256::from(1); -// let initial_deposit = 5_000_000_000; // 5 TAO -// let max_lending_cap = 4_000_000_000; // 4 TAO -// let emissions_share = 10; // 10% - -// assert_err!( -// SubtensorModule::create_subnet_lending_pool( -// RuntimeOrigin::signed(creator_coldkey), -// initial_deposit, -// max_lending_cap, -// emissions_share -// ), -// Error::::LendingPoolLendingCapInferiorToInitialDeposit -// ); -// }); -// } - -// #[test] -// fn test_create_subnet_lending_pool_fails_if_lending_cap_too_high() { -// new_test_ext(1).execute_with(|| { -// let creator_coldkey = U256::from(1); -// let initial_deposit = 2_000_000_000; // 2 TAO -// let max_lending_cap = 2_000_000_000_000; // 2000 TAO -// let emissions_share = 10; // 10% - -// assert_err!( -// SubtensorModule::create_subnet_lending_pool( -// RuntimeOrigin::signed(creator_coldkey), -// initial_deposit, -// max_lending_cap, -// emissions_share -// ), -// Error::::LendingPoolLendingCapTooHigh -// ); -// }); -// } - -// #[test] -// fn test_create_subnet_lending_pool_fails_if_emissions_share_too_low() { -// new_test_ext(1).execute_with(|| { -// let creator_coldkey = U256::from(1); -// let initial_deposit = 2_000_000_000; // 2 TAO -// let max_lending_cap = 100_000_000_000; // 100 TAO -// let emissions_share = 4; // 4% - -// assert_err!( -// SubtensorModule::create_subnet_lending_pool( -// RuntimeOrigin::signed(creator_coldkey), -// initial_deposit, -// max_lending_cap, -// emissions_share -// ), -// Error::::LendingPoolEmissionsShareTooLow -// ); -// }); -// } - -// #[test] -// fn test_create_subnet_lending_pool_fails_if_emissions_share_too_high() { -// new_test_ext(1).execute_with(|| { -// let creator_coldkey = U256::from(1); -// let initial_deposit = 2_000_000_000; // 2 TAO -// let max_lending_cap = 100_000_000_000; // 100 TAO -// let emissions_share = 101; // 101% - -// assert_err!( -// SubtensorModule::create_subnet_lending_pool( -// RuntimeOrigin::signed(creator_coldkey), -// initial_deposit, -// max_lending_cap, -// emissions_share -// ), -// Error::::LendingPoolEmissionsShareTooHigh -// ); -// }); -// } - -// #[test] -// fn create_subnet_lending_pool_fails_if_creator_coldkey_does_not_contains_initial_deposit() { -// new_test_ext(1).execute_with(|| { -// let creator_coldkey = U256::from(1); -// let initial_deposit = 2_000_000_000; // 2 TAO -// let max_lending_cap = 100_000_000_000; // 100 TAO -// let emissions_share = 10; // 10% - -// assert_err!( -// SubtensorModule::create_subnet_lending_pool( -// RuntimeOrigin::signed(creator_coldkey), -// initial_deposit, -// max_lending_cap, -// emissions_share -// ), -// Error::::LendingPoolNotEnoughBalanceToPayInitialDeposit -// ); -// }); -// } diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 4271bb1c03..0d979a6126 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -185,12 +185,6 @@ parameter_types! { pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks - pub const MinCrowdloanBlocksDuration: u64 = 20; // 20 blocks - pub const MinCrowdloanInitialDeposit: u64 = 2_000_000_000; // 2 TAO - pub const LendingPoolsLimit: u32 = 5; // 5 lending pools - pub const LendingPoolMinInitialDeposit: u64 = 1_000_000_000; // 1 TAO - pub const LendingPoolMaxLendingCap: u64 = 1_000_000_000_000; // 1000 TAO - pub const LendingPoolMinEmissionsShare: u64 = 5; // 5% } // Configure collective pallet for council @@ -414,12 +408,6 @@ impl crate::Config for Test { type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; - type MinCrowdloanBlocksDuration = MinCrowdloanBlocksDuration; - type MinCrowdloanInitialDeposit = MinCrowdloanInitialDeposit; - type LendingPoolsLimit = LendingPoolsLimit; - type LendingPoolMinInitialDeposit = LendingPoolMinInitialDeposit; - type LendingPoolMaxLendingCap = LendingPoolMaxLendingCap; - type LendingPoolMinEmissionsShare = LendingPoolMinEmissionsShare; } pub struct OriginPrivilegeCmp; diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 8964bffceb..6865c9fa49 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -5,7 +5,6 @@ mod delegate_info; mod difficulty; mod emission; mod epoch; -mod crowdloan; mod math; mod migration; mod mock; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e27589b674..25799a75c1 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1018,10 +1018,6 @@ parameter_types! { pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks - pub const LendingPoolsLimit: u32 = 10_000; // 10 000 lending pools - pub const LendingPoolMinInitialDeposit: u64 = 10_000_000_000; // 10 TAO - pub const LendingPoolMaxLendingCap: u64 = 100_000_000_000_000; // 100 000 TAO - pub const LendingPoolMinEmissionsShare: u64 = 5; // 5% } impl pallet_subtensor::Config for Runtime { @@ -1086,10 +1082,6 @@ impl pallet_subtensor::Config for Runtime { type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; - type LendingPoolsLimit = LendingPoolsLimit; - type LendingPoolMinInitialDeposit = LendingPoolMinInitialDeposit; - type LendingPoolMaxLendingCap = LendingPoolMaxLendingCap; - type LendingPoolMinEmissionsShare = LendingPoolMinEmissionsShare; } use sp_runtime::BoundedVec; From 549724fe73644d2af313ccd1c77b11c795760be1 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 31 Mar 2025 17:18:43 +0200 Subject: [PATCH 09/70] wip refacto + withdraw --- pallets/crowdloan/src/lib.rs | 97 ++++++++++++++++++++++++++-------- pallets/crowdloan/src/tests.rs | 20 +++---- 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 5dd5d412a8..17bc0bb616 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -1,18 +1,20 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; +use frame_support::pallet_prelude::*; use frame_support::{ PalletId, dispatch::GetDispatchInfo, sp_runtime::RuntimeDebug, traits::{Currency, Get, IsSubType, ReservableCurrency, tokens::ExistenceRequirement}, }; +use frame_system::pallet_prelude::*; use scale_info::TypeInfo; pub use pallet::*; use sp_runtime::traits::{AccountIdConversion, CheckedAdd, Zero}; -type CrowdloanIndex = u32; +type CrowdloanId = u32; mod tests; @@ -29,12 +31,14 @@ pub struct CrowdloanInfo { pub raised: Balance, } +type CrowdloanInfoOf = + CrowdloanInfo<::AccountId, BalanceOf, BlockNumberFor>; + #[frame_support::pallet(dev_mode)] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, sp_runtime::traits::Dispatchable}; - use frame_system::pallet_prelude::{BlockNumberFor, *}; - use sp_runtime::traits::CheckedSub; + use frame_support::sp_runtime::traits::Dispatchable; + use sp_runtime::traits::{CheckedSub, Saturating}; #[pallet::pallet] pub struct Pallet(_); @@ -72,19 +76,19 @@ pub mod pallet { pub type Crowdloans = StorageMap< _, Identity, - CrowdloanIndex, + CrowdloanId, CrowdloanInfo, BlockNumberFor>, OptionQuery, >; #[pallet::storage] - pub type NextCrowdloanIndex = StorageValue<_, CrowdloanIndex, ValueQuery, ConstU32<0>>; + pub type NextCrowdloanId = StorageValue<_, CrowdloanId, ValueQuery, ConstU32<0>>; #[pallet::storage] pub type Contributions = StorageDoubleMap< _, Identity, - CrowdloanIndex, + CrowdloanId, Identity, T::AccountId, BalanceOf, @@ -95,13 +99,18 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { Created { - crowdloan_id: CrowdloanIndex, + crowdloan_id: CrowdloanId, depositor: T::AccountId, end: BlockNumberFor, cap: BalanceOf, }, Contributed { - crowdloan_id: CrowdloanIndex, + crowdloan_id: CrowdloanId, + contributor: T::AccountId, + amount: BalanceOf, + }, + Withdrew { + crowdloan_id: CrowdloanId, contributor: T::AccountId, amount: BalanceOf, }, @@ -117,11 +126,15 @@ pub mod pallet { BlockDurationTooLong, InsufficientBalance, Overflow, - InvalidCrowdloanIndex, + InvalidCrowdloanId, CapRaised, CapExceeded, ContributionPeriodEnded, ContributionTooLow, + InvalidOrigin, + AlreadyFinalized, + ContributionPeriodNotEnded, + NoContribution, } #[pallet::call] @@ -167,11 +180,11 @@ pub mod pallet { // Ensure the depositor has enough balance to pay the deposit. ensure!( - T::Currency::free_balance(&depositor) >= deposit, + CurrencyOf::::free_balance(&depositor) >= deposit, Error::::InsufficientBalance ); - let crowdloan_id = NextCrowdloanIndex::::get(); + let crowdloan_id = NextCrowdloanId::::get(); let next_crowdloan_id = crowdloan_id.checked_add(1).ok_or(Error::::Overflow)?; Crowdloans::::insert( @@ -186,11 +199,11 @@ pub mod pallet { }, ); - NextCrowdloanIndex::::put(next_crowdloan_id); + NextCrowdloanId::::put(next_crowdloan_id); // Transfer the deposit to the crowdloan account frame_system::Pallet::::inc_providers(&Self::crowdloan_account_id(crowdloan_id)); - T::Currency::transfer( + CurrencyOf::::transfer( &depositor, &Self::crowdloan_account_id(crowdloan_id), deposit, @@ -206,6 +219,7 @@ pub mod pallet { end, cap, }); + Ok(()) } @@ -213,14 +227,11 @@ pub mod pallet { #[pallet::call_index(1)] pub fn contribute( origin: OriginFor, - #[pallet::compact] crowdloan_id: CrowdloanIndex, + #[pallet::compact] crowdloan_id: CrowdloanId, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let contributor = ensure_signed(origin)?; - - // Ensure the crowdloan exists - let mut crowdloan = - Crowdloans::::get(crowdloan_id).ok_or(Error::::InvalidCrowdloanIndex)?; + let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; // Ensure the crowdloan has not ended let now = frame_system::Pallet::::block_number(); @@ -269,7 +280,47 @@ pub mod pallet { /// Refund every contributor's balance from a crowdloan #[pallet::call_index(3)] - pub fn refund(origin: OriginFor) -> DispatchResult { + pub fn withdraw( + origin: OriginFor, + contributor: T::AccountId, + #[pallet::compact] crowdloan_id: CrowdloanId, + ) -> DispatchResult { + ensure_signed(origin)?; + + let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + + // Ensure the contribution period has ended + let now = frame_system::Pallet::::block_number(); + ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); + + // Ensure the crowdloan hasn't raised the cap + ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); + + // Ensure the contributor has a contribution + let amount = Contributions::::get(&crowdloan_id, &contributor) + .unwrap_or_else(|| Zero::zero()); + ensure!(amount > Zero::zero(), Error::::NoContribution); + + CurrencyOf::::transfer( + &Self::crowdloan_account_id(crowdloan_id), + &contributor, + amount, + ExistenceRequirement::AllowDeath, + )?; + + // Remove the contribution from the contributions map and update + // tracked refund so far + Contributions::::remove(&crowdloan_id, &contributor); + crowdloan.raised = crowdloan.raised.saturating_sub(amount); + + Crowdloans::::insert(crowdloan_id, &crowdloan); + + Self::deposit_event(Event::::Withdrew { + contributor, + crowdloan_id, + amount, + }); + Ok(()) } @@ -288,7 +339,11 @@ pub mod pallet { } impl Pallet { - pub fn crowdloan_account_id(id: CrowdloanIndex) -> T::AccountId { + fn crowdloan_account_id(id: CrowdloanId) -> T::AccountId { T::PalletId::get().into_sub_account_truncating(id) } + + fn ensure_crowdloan_exists(crowdloan_id: CrowdloanId) -> Result, Error> { + Crowdloans::::get(crowdloan_id).ok_or(Error::::InvalidCrowdloanId) + } } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 3138758082..b32287f57b 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -8,7 +8,7 @@ use frame_system::pallet_prelude::BlockNumberFor; use sp_core::U256; use sp_runtime::{BuildStorage, DispatchError, traits::IdentityLookup}; -use crate::{BalanceOf, CrowdloanIndex, CrowdloanInfo, pallet as pallet_crowdloan}; +use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, pallet as pallet_crowdloan}; type Block = frame_system::mocking::MockBlock; type AccountOf = ::AccountId; @@ -36,7 +36,7 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub const CrowdloanId: PalletId = PalletId(*b"bt/cloan"); + pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); pub const MinimumDeposit: u64 = 50; pub const AbsoluteMinimumContribution: u64 = 10; pub const MinimumBlockDuration: u64 = 20; @@ -44,7 +44,7 @@ parameter_types! { } impl pallet_crowdloan::Config for Test { - type PalletId = CrowdloanId; + type PalletId = CrowdloanPalletId; type Currency = Balances; type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; @@ -391,7 +391,7 @@ fn test_contribute_succeeds() { run_to_block(10); // first contribution to the crowdloan from depositor - let crowdloan_id: CrowdloanIndex = 0; + let crowdloan_id: CrowdloanId = 0; let amount: BalanceOf = 50; assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(depositor), @@ -472,12 +472,12 @@ fn test_contribute_fails_if_crowdloan_does_not_exist() { .with_balance(U256::from(1), 100) .build_and_execute(|| { let contributor: AccountOf = U256::from(1); - let crowdloan_id: CrowdloanIndex = 0; + let crowdloan_id: CrowdloanId = 0; let amount: BalanceOf = 20; assert_err!( Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), - pallet_crowdloan::Error::::InvalidCrowdloanIndex + pallet_crowdloan::Error::::InvalidCrowdloanId ); }); } @@ -507,7 +507,7 @@ fn test_contribute_fails_if_crowdloan_has_ended() { // contribute to the crowdloan let contributor: AccountOf = U256::from(2); - let crowdloan_id: CrowdloanIndex = 0; + let crowdloan_id: CrowdloanId = 0; let amount: BalanceOf = 20; assert_err!( Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), @@ -541,7 +541,7 @@ fn test_contribute_fails_if_cap_has_been_raised() { run_to_block(10); // first contribution to the crowdloan fully raise the cap - let crowdloan_id: CrowdloanIndex = 0; + let crowdloan_id: CrowdloanId = 0; let contributor1: AccountOf = U256::from(2); let amount: BalanceOf = cap - initial_deposit; assert_ok!(Crowdloan::contribute( @@ -585,7 +585,7 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { // contribute to the crowdloan let contributor: AccountOf = U256::from(2); - let crowdloan_id: CrowdloanIndex = 0; + let crowdloan_id: CrowdloanId = 0; let amount: BalanceOf = 5; assert_err!( Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), @@ -619,7 +619,7 @@ fn test_contribute_fails_if_contribution_will_make_the_raised_amount_exceed_the_ // contribute to the crowdloan let contributor: AccountOf = U256::from(2); - let crowdloan_id: CrowdloanIndex = 0; + let crowdloan_id: CrowdloanId = 0; let amount: BalanceOf = 300; assert_err!( Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), From b5f9a2192aaf652605d19a595788029a9eec1045 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 31 Mar 2025 18:01:05 +0200 Subject: [PATCH 10/70] added tests for withdraw --- pallets/crowdloan/src/lib.rs | 21 +- pallets/crowdloan/src/tests.rs | 349 ++++++++++++++++++++++++++++++++- 2 files changed, 350 insertions(+), 20 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 17bc0bb616..0a6a2aa48c 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -254,6 +254,13 @@ pub mod pallet { .ok_or(Error::::Overflow)?; ensure!(crowdloan.raised <= crowdloan.cap, Error::::CapExceeded); + CurrencyOf::::transfer( + &contributor, + &Self::crowdloan_account_id(crowdloan_id), + amount, + ExistenceRequirement::AllowDeath, + )?; + // Ensure the contribution does not overflow the contributor's balance and update // the contribution let contribution = Contributions::::get(&crowdloan_id, &contributor) @@ -269,17 +276,11 @@ pub mod pallet { crowdloan_id, amount, }); - Ok(()) - } - - /// Withdraw all contributior balance from a crowdloan - #[pallet::call_index(2)] - pub fn withdraw(origin: OriginFor) -> DispatchResult { - Ok(()) + + Ok(()) } - /// Refund every contributor's balance from a crowdloan - #[pallet::call_index(3)] + #[pallet::call_index(3)] pub fn withdraw( origin: OriginFor, contributor: T::AccountId, @@ -309,7 +310,7 @@ pub mod pallet { )?; // Remove the contribution from the contributions map and update - // tracked refund so far + // tracked refunds so far Contributions::::remove(&crowdloan_id, &contributor); crowdloan.raised = crowdloan.raised.saturating_sub(amount); diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index b32287f57b..b22b256322 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -407,6 +407,10 @@ fn test_contribute_succeeds() { } .into() ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, depositor), + Some(100) + ); // second contribution to the crowdloan let contributor1: AccountOf = U256::from(2); @@ -425,6 +429,10 @@ fn test_contribute_succeeds() { } .into() ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, contributor1), + Some(100) + ); // third contribution to the crowdloan let contributor2: AccountOf = U256::from(3); @@ -443,21 +451,19 @@ fn test_contribute_succeeds() { } .into() ); - - // ensure the contributions are updated correctly - assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, depositor), - Some(100) - ); - assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, contributor1), - Some(100) - ); assert_eq!( pallet_crowdloan::Contributions::::get(crowdloan_id, contributor2), Some(50) ); + // ensure the contributions are present in the crowdloan account + let crowdloan_account_id: AccountOf = + pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); + assert_eq!( + pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + 250 + ); + // ensure the crowdloan raised amount is updated correctly assert!( pallet_crowdloan::Crowdloans::::get(crowdloan_id) @@ -627,3 +633,326 @@ fn test_contribute_fails_if_contribution_will_make_the_raised_amount_exceed_the_ ); }); } + +#[test] +fn test_withdraw_succeeds() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .with_balance(U256::from(3), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // contribute to the crowdloan + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // withdraw from depositor + assert_ok!(Crowdloan::withdraw( + RuntimeOrigin::signed(depositor), + depositor, + crowdloan_id + )); + // ensure the depositor has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(&depositor), + 100 + ); + + // withdraw from contributor + assert_ok!(Crowdloan::withdraw( + RuntimeOrigin::signed(contributor), + contributor, + crowdloan_id + )); + // ensure the contributor has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(&contributor), + 100 + ); + + // ensure the crowdloan account has the correct amount + let crowdloan_account_id: AccountOf = + pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); + assert_eq!( + pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + 0 + ); + // ensure the crowdloan raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 0) + ); + }); +} + +#[test] +fn test_withdraw_succeeds_for_another_contributor() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // contribute to the crowdloan + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // withdraw for depositor as a contributor + assert_ok!(Crowdloan::withdraw( + RuntimeOrigin::signed(contributor), + depositor, + crowdloan_id + )); + + // ensure the depositor has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(&depositor), + 100 + ); + + // ensure the contributor has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(&contributor), + 0 + ); + + // ensure the crowdloan account has the correct amount + let crowdloan_account_id: AccountOf = + pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); + assert_eq!( + pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + 100 + ); + + // ensure the crowdloan raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 100) + ); + }); +} + +#[test] +fn test_withdraw_fails_if_crowdloan_does_not_exists() { + TestState::default().build_and_execute(|| { + let contributor: AccountOf = U256::from(1); + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::withdraw( + RuntimeOrigin::signed(contributor), + contributor, + crowdloan_id + ), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[test] +fn test_withdraw_fails_if_contribution_period_has_not_ended() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // contribute to the crowdloan + let contributor: AccountOf = U256::from(2); + let crowdloan_id: CrowdloanId = 0; + let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks + run_to_block(20); + + // try to withdraw + assert_err!( + Crowdloan::withdraw( + RuntimeOrigin::signed(contributor), + contributor, + crowdloan_id + ), + pallet_crowdloan::Error::::ContributionPeriodNotEnded + ); + }); +} + +#[test] +fn test_withdraw_fails_if_cap_was_fully_raised() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 200) + .with_balance(U256::from(3), 200) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // contribute to the crowdloan + let contributor: AccountOf = U256::from(2); + let crowdloan_id: CrowdloanId = 0; + let amount: BalanceOf = 150; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks + run_to_block(20); + + // another contribution to the crowdloan + let contributor2: AccountOf = U256::from(3); + let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor2), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // try to withdraw + assert_err!( + Crowdloan::withdraw( + RuntimeOrigin::signed(contributor), + contributor, + crowdloan_id + ), + pallet_crowdloan::Error::::CapRaised + ); + }); +} + +#[test] +fn test_withdraw_fails_if_contribution_is_not_found() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 200) + .with_balance(U256::from(3), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let minimum_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + minimum_contribution, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // contribute to the crowdloan + let contributor: AccountOf = U256::from(2); + let crowdloan_id: CrowdloanId = 0; + let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // try to withdraw + let contributor2: AccountOf = U256::from(3); + assert_err!( + Crowdloan::withdraw( + RuntimeOrigin::signed(contributor2), + contributor2, + crowdloan_id + ), + pallet_crowdloan::Error::::NoContribution + ); + }); +} From d5be5eeef866af61c9d575b1f6091217d167eb8d Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 1 Apr 2025 13:06:37 +0200 Subject: [PATCH 11/70] added refund logic + tests --- pallets/crowdloan/src/lib.rs | 117 +++++++--- pallets/crowdloan/src/tests.rs | 377 ++++++++++++++++++++++++--------- 2 files changed, 354 insertions(+), 140 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 0a6a2aa48c..08c81f083f 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -21,11 +21,10 @@ mod tests; type CurrencyOf = ::Currency; type BalanceOf = as Currency<::AccountId>>::Balance; -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct CrowdloanInfo { pub depositor: AccountId, pub deposit: Balance, - pub minimum_contribution: Balance, pub end: BlockNumber, pub cap: Balance, pub raised: Balance, @@ -63,23 +62,21 @@ pub mod pallet { type MinimumDeposit: Get>; #[pallet::constant] - type AbsoluteMinimumContribution: Get>; + type MinimumContribution: Get>; #[pallet::constant] type MinimumBlockDuration: Get>; #[pallet::constant] type MaximumBlockDuration: Get>; + + #[pallet::constant] + type RefundContributorsLimit: Get; } #[pallet::storage] - pub type Crowdloans = StorageMap< - _, - Identity, - CrowdloanId, - CrowdloanInfo, BlockNumberFor>, - OptionQuery, - >; + pub type Crowdloans = + StorageMap<_, Identity, CrowdloanId, CrowdloanInfoOf, OptionQuery>; #[pallet::storage] pub type NextCrowdloanId = StorageValue<_, CrowdloanId, ValueQuery, ConstU32<0>>; @@ -114,14 +111,19 @@ pub mod pallet { contributor: T::AccountId, amount: BalanceOf, }, + PartiallyRefunded { + crowdloan_id: CrowdloanId, + }, + Refunded { + crowdloan_id: CrowdloanId, + }, } #[pallet::error] pub enum Error { DepositTooLow, CapTooLow, - MinimumContributionTooLow, - CannotEndInPast, + CannotEndInPast, BlockDurationTooShort, BlockDurationTooLong, InsufficientBalance, @@ -144,8 +146,7 @@ pub mod pallet { pub fn create( origin: OriginFor, #[pallet::compact] deposit: BalanceOf, - #[pallet::compact] minimum_contribution: BalanceOf, - #[pallet::compact] cap: BalanceOf, + #[pallet::compact] cap: BalanceOf, #[pallet::compact] end: BlockNumberFor, ) -> DispatchResult { let depositor = ensure_signed(origin)?; @@ -158,12 +159,6 @@ pub mod pallet { ); ensure!(cap > deposit, Error::::CapTooLow); - // Ensure the minimum contribution is at least the absolute minimum contribution - ensure!( - minimum_contribution >= T::AbsoluteMinimumContribution::get(), - Error::::MinimumContributionTooLow - ); - // Ensure the end block is after the current block and the duration is // between the minimum and maximum block duration let now = frame_system::Pallet::::block_number(); @@ -192,8 +187,7 @@ pub mod pallet { CrowdloanInfo { depositor: depositor.clone(), deposit, - minimum_contribution, - end, + end, cap, raised: deposit, }, @@ -231,18 +225,18 @@ pub mod pallet { #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let contributor = ensure_signed(origin)?; + let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; // Ensure the crowdloan has not ended let now = frame_system::Pallet::::block_number(); ensure!(crowdloan.end > now, Error::::ContributionPeriodEnded); - // Ensure the cap has not been fully raised - ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); + Self::ensure_crowdloan_has_not_fully_raised(&crowdloan)?; // Ensure the contribution is at least the minimum contribution ensure!( - amount >= crowdloan.minimum_contribution, + amount >= T::MinimumContribution::get(), Error::::ContributionTooLow ); @@ -289,13 +283,8 @@ pub mod pallet { ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; - - // Ensure the contribution period has ended - let now = frame_system::Pallet::::block_number(); - ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); - - // Ensure the crowdloan hasn't raised the cap - ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); +Self::ensure_crowdloan_ended(&crowdloan)?; + Self::ensure_crowdloan_has_not_fully_raised(&crowdloan)?; // Ensure the contributor has a contribution let amount = Contributions::::get(&crowdloan_id, &contributor) @@ -325,9 +314,54 @@ pub mod pallet { Ok(()) } - /// Remove fund after end and refunds - #[pallet::call_index(4)] - pub fn dissolve(origin: OriginFor) -> DispatchResult { + #[pallet::call_index(4)] + pub fn refund( +origin: OriginFor, + #[pallet::compact] crowdloan_id: CrowdloanId, +) -> DispatchResult { +ensure_signed(origin)?; + + let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + let crowdloan_account = Self::crowdloan_account_id(crowdloan_id); + Self::ensure_crowdloan_ended(&crowdloan)?; + Self::ensure_crowdloan_has_not_fully_raised(&crowdloan)?; + + let mut refunded_contributors: Vec = vec![]; + let mut refund_count = 0; + // Assume everyone can be refunded + let mut all_refunded = true; + let contributions = Contributions::::iter_prefix(crowdloan_id); + for (contributor, amount) in contributions { + if refund_count >= T::RefundContributorsLimit::get() { + // Not everyone can be refunded + all_refunded = false; + break; + } + + CurrencyOf::::transfer( + &crowdloan_account, + &contributor, + amount, + ExistenceRequirement::AllowDeath, + )?; + refunded_contributors.push(contributor); + crowdloan.raised = crowdloan.raised.saturating_sub(amount); + refund_count += 1; + } + + Crowdloans::::insert(crowdloan_id, &crowdloan); + + // Clear refunded contributors + for contributor in refunded_contributors { + Contributions::::remove(&crowdloan_id, &contributor); + } + + if all_refunded { + Self::deposit_event(Event::::Refunded { crowdloan_id }); + } else { + Self::deposit_event(Event::::PartiallyRefunded { crowdloan_id }); + } + Ok(()) } @@ -347,4 +381,17 @@ impl Pallet { fn ensure_crowdloan_exists(crowdloan_id: CrowdloanId) -> Result, Error> { Crowdloans::::get(crowdloan_id).ok_or(Error::::InvalidCrowdloanId) } + + fn ensure_crowdloan_ended(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { + let now = frame_system::Pallet::::block_number(); + ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); + Ok(()) + } + + fn ensure_crowdloan_has_not_fully_raised( + crowdloan: &CrowdloanInfoOf, + ) -> Result<(), Error> { + ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); + Ok(()) + } } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index b22b256322..f6e8ecf8de 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -38,9 +38,10 @@ impl pallet_balances::Config for Test { parameter_types! { pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); pub const MinimumDeposit: u64 = 50; - pub const AbsoluteMinimumContribution: u64 = 10; + pub const MinimumContribution: u64 = 10; pub const MinimumBlockDuration: u64 = 20; pub const MaximumBlockDuration: u64 = 100; + pub const RefundContributorsLimit: u32 = 2; } impl pallet_crowdloan::Config for Test { @@ -49,9 +50,10 @@ impl pallet_crowdloan::Config for Test { type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; type MinimumDeposit = MinimumDeposit; - type AbsoluteMinimumContribution = AbsoluteMinimumContribution; + type MinimumContribution = MinimumContribution; type MinimumBlockDuration = MinimumBlockDuration; type MaximumBlockDuration = MaximumBlockDuration; + type RefundContributorsLimit = RefundContributorsLimit; } pub(crate) struct TestState { @@ -123,14 +125,12 @@ fn test_create_succeeds() { .build_and_execute(|| { let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), deposit, - minimum_contribution, cap, end, )); @@ -142,7 +142,6 @@ fn test_create_succeeds() { Some(CrowdloanInfo { depositor, deposit, - minimum_contribution, cap, end, raised: deposit, @@ -178,18 +177,11 @@ fn test_create_succeeds() { fn test_create_fails_if_bad_origin() { TestState::default().build_and_execute(|| { let deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_err!( - Crowdloan::create( - RuntimeOrigin::none(), - deposit, - minimum_contribution, - cap, - end - ), + Crowdloan::create(RuntimeOrigin::none(), deposit, cap, end), DispatchError::BadOrigin ); }); @@ -202,18 +194,11 @@ fn test_create_fails_if_deposit_is_too_low() { .build_and_execute(|| { let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 20; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_err!( - Crowdloan::create( - RuntimeOrigin::signed(depositor), - deposit, - minimum_contribution, - cap, - end - ), + Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), pallet_crowdloan::Error::::DepositTooLow ); }); @@ -226,47 +211,16 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { .build_and_execute(|| { let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 40; let end: BlockNumberFor = 50; assert_err!( - Crowdloan::create( - RuntimeOrigin::signed(depositor), - deposit, - minimum_contribution, - cap, - end - ), + Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), pallet_crowdloan::Error::::CapTooLow ); }); } -#[test] -fn test_create_fails_if_minimum_contribution_is_too_low() { - TestState::default() - .with_balance(U256::from(1), 100) - .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); - let deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 5; - let cap: BalanceOf = 300; - let end: BlockNumberFor = 50; - - assert_err!( - Crowdloan::create( - RuntimeOrigin::signed(depositor), - deposit, - minimum_contribution, - cap, - end - ), - pallet_crowdloan::Error::::MinimumContributionTooLow - ); - }); -} - #[test] fn test_create_fails_if_end_is_in_the_past() { let current_block_number: BlockNumberFor = 10; @@ -277,18 +231,11 @@ fn test_create_fails_if_end_is_in_the_past() { .build_and_execute(|| { let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = current_block_number - 5; assert_err!( - Crowdloan::create( - RuntimeOrigin::signed(depositor), - deposit, - minimum_contribution, - cap, - end - ), + Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), pallet_crowdloan::Error::::CannotEndInPast ); }); @@ -301,18 +248,11 @@ fn test_create_fails_if_block_duration_is_too_short() { .build_and_execute(|| { let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 11; assert_err!( - Crowdloan::create( - RuntimeOrigin::signed(depositor), - deposit, - minimum_contribution, - cap, - end - ), + Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), pallet_crowdloan::Error::::BlockDurationTooShort ); }); @@ -325,18 +265,11 @@ fn test_create_fails_if_block_duration_is_too_long() { .build_and_execute(|| { let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 1000; assert_err!( - Crowdloan::create( - RuntimeOrigin::signed(depositor), - deposit, - minimum_contribution, - cap, - end - ), + Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), pallet_crowdloan::Error::::BlockDurationTooLong ); }); @@ -349,18 +282,11 @@ fn test_create_fails_if_depositor_has_insufficient_balance() { .build_and_execute(|| { let depositor: AccountOf = U256::from(1); let deposit: BalanceOf = 200; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_err!( - Crowdloan::create( - RuntimeOrigin::signed(depositor), - deposit, - minimum_contribution, - cap, - end - ), + Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), pallet_crowdloan::Error::::InsufficientBalance ); }); @@ -376,13 +302,11 @@ fn test_contribute_succeeds() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -497,13 +421,11 @@ fn test_contribute_fails_if_crowdloan_has_ended() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -532,13 +454,11 @@ fn test_contribute_fails_if_cap_has_been_raised() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -575,13 +495,11 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -609,13 +527,11 @@ fn test_contribute_fails_if_contribution_will_make_the_raised_amount_exceed_the_ // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -644,13 +560,11 @@ fn test_withdraw_succeeds() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -719,13 +633,11 @@ fn test_withdraw_succeeds_for_another_contributor() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -807,13 +719,11 @@ fn test_withdraw_fails_if_contribution_period_has_not_ended() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -856,13 +766,11 @@ fn test_withdraw_fails_if_cap_was_fully_raised() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -917,13 +825,11 @@ fn test_withdraw_fails_if_contribution_is_not_found() { // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let minimum_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, - minimum_contribution, cap, end )); @@ -956,3 +862,264 @@ fn test_withdraw_fails_if_contribution_is_not_found() { ); }); } + +#[test] +fn test_refund_succeeds() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .with_balance(U256::from(3), 100) + .with_balance(U256::from(4), 100) + .with_balance(U256::from(5), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // first contribution to the crowdloan + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // second contribution to the crowdloan + let contributor2: AccountOf = U256::from(3); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor2), + crowdloan_id, + amount + )); + + // third contribution to the crowdloan + let contributor3: AccountOf = U256::from(4); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor3), + crowdloan_id, + amount + )); + + // fourth contribution to the crowdloan + let contributor4: AccountOf = U256::from(5); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor4), + crowdloan_id, + amount, + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // first round of refund + assert_ok!(Crowdloan::refund( + RuntimeOrigin::signed(depositor), + crowdloan_id + )); + + // ensure the crowdloan account has the correct amount + let crowdloan_account_id: AccountOf = + pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); + assert_eq!( + pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + 150 // 2 contributors have been refunded so far + ); + // ensure raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 150) + ); + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::PartiallyRefunded { crowdloan_id }.into() + ); + + // run some more blocks + run_to_block(70); + + // second round of refund + assert_ok!(Crowdloan::refund( + RuntimeOrigin::signed(depositor), + crowdloan_id + )); + + // ensure the crowdloan account has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + 50 + ); + // ensure raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 50) + ); + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::PartiallyRefunded { crowdloan_id }.into() + ); + + // run some more blocks + run_to_block(80); + + // third round of refund + assert_ok!(Crowdloan::refund( + RuntimeOrigin::signed(depositor), + crowdloan_id + )); + + // ensure the crowdloan account has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + 0 + ); + // ensure the raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 0) + ); + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Refunded { crowdloan_id }.into() + ); + + // ensure depositor has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(&depositor), + 100 + ); + + // ensure each contributor has been refunded + assert_eq!( + pallet_balances::Pallet::::free_balance(&contributor), + 100 + ); + assert_eq!( + pallet_balances::Pallet::::free_balance(&contributor2), + 100 + ); + assert_eq!( + pallet_balances::Pallet::::free_balance(&contributor3), + 100 + ); + assert_eq!( + pallet_balances::Pallet::::free_balance(&contributor4), + 100 + ); + }) +} + +#[test] +fn test_refund_fails_if_crowdloan_does_not_exist() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let depositor: AccountOf = U256::from(1); + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::refund(RuntimeOrigin::signed(depositor), crowdloan_id), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[test] +fn test_refund_fails_if_crowdloan_has_not_ended() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // try to refund + let crowdloan_id: CrowdloanId = 0; + assert_err!( + Crowdloan::refund(RuntimeOrigin::signed(depositor), crowdloan_id), + pallet_crowdloan::Error::::ContributionPeriodNotEnded + ); + }); +} + +#[test] +fn test_refund_fails_if_crowdloan_has_fully_raised() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 200) + .with_balance(U256::from(3), 200) + .build_and_execute(|| { + let depositor: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + initial_deposit, + cap, + end + )); + + // run some blocks + run_to_block(10); + + // first contribution to the crowdloan + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 150; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks + run_to_block(20); + + // second contribution to the crowdloan + let contributor2: AccountOf = U256::from(3); + let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor2), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // try to refund + assert_err!( + Crowdloan::refund(RuntimeOrigin::signed(depositor), crowdloan_id), + pallet_crowdloan::Error::::CapRaised + ); + }); +} From 5c989a85b7991111cbe4af99d7eaa9eff98037a8 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 2 Apr 2025 13:45:22 +0200 Subject: [PATCH 12/70] refacto + fix tests --- pallets/crowdloan/src/lib.rs | 153 ++++++++++++++++++++++----------- pallets/crowdloan/src/tests.rs | 148 ++++++++++++++++++++++++++----- 2 files changed, 232 insertions(+), 69 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 08c81f083f..68fa451e01 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -5,14 +5,16 @@ use frame_support::pallet_prelude::*; use frame_support::{ PalletId, dispatch::GetDispatchInfo, - sp_runtime::RuntimeDebug, + sp_runtime::{ + RuntimeDebug, + traits::{AccountIdConversion, CheckedAdd, Zero}, + }, traits::{Currency, Get, IsSubType, ReservableCurrency, tokens::ExistenceRequirement}, }; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; pub use pallet::*; -use sp_runtime::traits::{AccountIdConversion, CheckedAdd, Zero}; type CrowdloanId = u32; @@ -22,16 +24,23 @@ type CurrencyOf = ::Currency; type BalanceOf = as Currency<::AccountId>>::Balance; #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct CrowdloanInfo { +pub struct CrowdloanInfo { pub depositor: AccountId, pub deposit: Balance, pub end: BlockNumber, pub cap: Balance, pub raised: Balance, + pub target_address: AccountId, + pub call: Box, + pub finalized: bool, } -type CrowdloanInfoOf = - CrowdloanInfo<::AccountId, BalanceOf, BlockNumberFor>; +type CrowdloanInfoOf = CrowdloanInfo< + ::AccountId, + BalanceOf, + BlockNumberFor, + ::RuntimeCall, +>; #[frame_support::pallet(dev_mode)] pub mod pallet { @@ -92,6 +101,9 @@ pub mod pallet { OptionQuery, >; + #[pallet::storage] + pub type CurrentCrowdloanId = StorageValue<_, CrowdloanId, OptionQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -123,7 +135,7 @@ pub mod pallet { pub enum Error { DepositTooLow, CapTooLow, - CannotEndInPast, + CannotEndInPast, BlockDurationTooShort, BlockDurationTooLong, InsufficientBalance, @@ -146,13 +158,15 @@ pub mod pallet { pub fn create( origin: OriginFor, #[pallet::compact] deposit: BalanceOf, - #[pallet::compact] cap: BalanceOf, + #[pallet::compact] cap: BalanceOf, #[pallet::compact] end: BlockNumberFor, + target_address: T::AccountId, + call: Box<::RuntimeCall>, ) -> DispatchResult { let depositor = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); // Ensure the deposit is at least the minimum deposit and cap is greater - // than the deposit ensure!( deposit >= T::MinimumDeposit::get(), Error::::DepositTooLow @@ -161,8 +175,7 @@ pub mod pallet { // Ensure the end block is after the current block and the duration is // between the minimum and maximum block duration - let now = frame_system::Pallet::::block_number(); - ensure!(end > now, Error::::CannotEndInPast); + ensure!(now < end, Error::::CannotEndInPast); let block_duration = end.checked_sub(&now).expect("checked end after now; qed"); ensure!( block_duration >= T::MinimumBlockDuration::get(), @@ -173,7 +186,7 @@ pub mod pallet { Error::::BlockDurationTooLong ); - // Ensure the depositor has enough balance to pay the deposit. + // Ensure the depositor has enough balance to pay the initial deposit ensure!( CurrencyOf::::free_balance(&depositor) >= deposit, Error::::InsufficientBalance @@ -187,15 +200,18 @@ pub mod pallet { CrowdloanInfo { depositor: depositor.clone(), deposit, - end, + end, cap, raised: deposit, + target_address, + call, + finalized: false, }, ); NextCrowdloanId::::put(next_crowdloan_id); - // Transfer the deposit to the crowdloan account + // Track the crowdloan account and transfer the deposit to the crowdloan account frame_system::Pallet::::inc_providers(&Self::crowdloan_account_id(crowdloan_id)); CurrencyOf::::transfer( &depositor, @@ -204,7 +220,6 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; - // Add initial deposit to contributions Contributions::::insert(&crowdloan_id, &depositor, deposit); Self::deposit_event(Event::::Created { @@ -225,28 +240,36 @@ pub mod pallet { #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let contributor = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; - // Ensure the crowdloan has not ended - let now = frame_system::Pallet::::block_number(); - ensure!(crowdloan.end > now, Error::::ContributionPeriodEnded); - - Self::ensure_crowdloan_has_not_fully_raised(&crowdloan)?; + // Ensure crowdloan has not ended and has not raised cap + ensure!(now < crowdloan.end, Error::::ContributionPeriodEnded); + ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); - // Ensure the contribution is at least the minimum contribution + // Ensure contribution is at least the minimum contribution ensure!( amount >= T::MinimumContribution::get(), Error::::ContributionTooLow ); - - // Ensure the contribution does not overflow the actual raised amount + // Ensure contribution does not overflow the actual raised amount // and it does not exceed the cap crowdloan.raised = crowdloan .raised .checked_add(&amount) .ok_or(Error::::Overflow)?; ensure!(crowdloan.raised <= crowdloan.cap, Error::::CapExceeded); + // Ensure contribution does not overflow the contributor's total contributions + let contribution = Contributions::::get(&crowdloan_id, &contributor) + .unwrap_or(Zero::zero()) + .checked_add(&amount) + .ok_or(Error::::Overflow)?; + // Ensure contributor has enough balance to pay + ensure!( + CurrencyOf::::free_balance(&contributor) >= amount, + Error::::InsufficientBalance + ); CurrencyOf::::transfer( &contributor, @@ -255,12 +278,6 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; - // Ensure the contribution does not overflow the contributor's balance and update - // the contribution - let contribution = Contributions::::get(&crowdloan_id, &contributor) - .unwrap_or(Zero::zero()) - .checked_add(&amount) - .ok_or(Error::::Overflow)?; Contributions::::insert(&crowdloan_id, &contributor, contribution); Crowdloans::::insert(crowdloan_id, &crowdloan); @@ -270,11 +287,11 @@ pub mod pallet { crowdloan_id, amount, }); - - Ok(()) + + Ok(()) } - #[pallet::call_index(3)] + #[pallet::call_index(3)] pub fn withdraw( origin: OriginFor, contributor: T::AccountId, @@ -283,10 +300,9 @@ pub mod pallet { ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; -Self::ensure_crowdloan_ended(&crowdloan)?; - Self::ensure_crowdloan_has_not_fully_raised(&crowdloan)?; + Self::ensure_crowdloan_failed(&crowdloan)?; - // Ensure the contributor has a contribution + // Ensure contributor has balance left in the crowdloan account let amount = Contributions::::get(&crowdloan_id, &contributor) .unwrap_or_else(|| Zero::zero()); ensure!(amount > Zero::zero(), Error::::NoContribution); @@ -299,7 +315,7 @@ Self::ensure_crowdloan_ended(&crowdloan)?; )?; // Remove the contribution from the contributions map and update - // tracked refunds so far + // refunds so far Contributions::::remove(&crowdloan_id, &contributor); crowdloan.raised = crowdloan.raised.saturating_sub(amount); @@ -314,17 +330,16 @@ Self::ensure_crowdloan_ended(&crowdloan)?; Ok(()) } - #[pallet::call_index(4)] + #[pallet::call_index(4)] pub fn refund( -origin: OriginFor, + origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, -) -> DispatchResult { -ensure_signed(origin)?; + ) -> DispatchResult { + ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; let crowdloan_account = Self::crowdloan_account_id(crowdloan_id); - Self::ensure_crowdloan_ended(&crowdloan)?; - Self::ensure_crowdloan_has_not_fully_raised(&crowdloan)?; + Self::ensure_crowdloan_failed(&crowdloan)?; let mut refunded_contributors: Vec = vec![]; let mut refund_count = 0; @@ -344,6 +359,7 @@ ensure_signed(origin)?; amount, ExistenceRequirement::AllowDeath, )?; + refunded_contributors.push(contributor); crowdloan.raised = crowdloan.raised.saturating_sub(amount); refund_count += 1; @@ -365,10 +381,34 @@ ensure_signed(origin)?; Ok(()) } - /// Edit configuration + // Finish #[pallet::call_index(5)] - pub fn edit(origin: OriginFor) -> DispatchResult { - Ok(()) + pub fn finalize( + origin: OriginFor, + #[pallet::compact] crowdloan_id: CrowdloanId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + Self::ensure_crowdloan_succeeded(&crowdloan)?; + + ensure!(who == crowdloan.depositor, Error::::InvalidOrigin); + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + + CurrencyOf::::transfer( + &Self::crowdloan_account_id(crowdloan_id), + &crowdloan.target_address, + crowdloan.raised, + ExistenceRequirement::AllowDeath, + )?; + + CurrentCrowdloanId::::put(crowdloan_id); + + // crowdloan.call.dispatch(origin)?; + + CurrentCrowdloanId::::kill(); + + Ok(().into()) } } } @@ -382,16 +422,31 @@ impl Pallet { Crowdloans::::get(crowdloan_id).ok_or(Error::::InvalidCrowdloanId) } - fn ensure_crowdloan_ended(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { + fn ensure_crowdloan_failed(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { + // Has ended let now = frame_system::Pallet::::block_number(); ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); + + // Has not raised cap + ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); + + // Has not finalized + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + Ok(()) } - fn ensure_crowdloan_has_not_fully_raised( - crowdloan: &CrowdloanInfoOf, - ) -> Result<(), Error> { - ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); + fn ensure_crowdloan_succeeded(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { + // Has ended + let now = frame_system::Pallet::::block_number(); + ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); + + // Has raised cap + ensure!(crowdloan.raised == crowdloan.cap, Error::::CapRaised); + + // Has not finalized + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + Ok(()) } } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index f6e8ecf8de..b74e7141b8 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -118,6 +118,12 @@ pub(crate) fn run_to_block(n: u64) { } } +fn noop_call() -> Box { + Box::new(RuntimeCall::System(frame_system::Call::::remark { + remark: vec![], + })) +} + #[test] fn test_create_succeeds() { TestState::default() @@ -127,12 +133,15 @@ fn test_create_succeeds() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), deposit, cap, end, + target_address, + noop_call(), )); let crowdloan_id = 0; @@ -145,6 +154,9 @@ fn test_create_succeeds() { cap, end, raised: deposit, + target_address, + call: noop_call(), + finalized: false, }) ); // ensure the crowdloan account has the deposit @@ -179,9 +191,17 @@ fn test_create_fails_if_bad_origin() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_err!( - Crowdloan::create(RuntimeOrigin::none(), deposit, cap, end), + Crowdloan::create( + RuntimeOrigin::none(), + deposit, + cap, + end, + target_address, + noop_call() + ), DispatchError::BadOrigin ); }); @@ -196,9 +216,17 @@ fn test_create_fails_if_deposit_is_too_low() { let deposit: BalanceOf = 20; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_err!( - Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), + Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + ), pallet_crowdloan::Error::::DepositTooLow ); }); @@ -213,9 +241,17 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { let deposit: BalanceOf = 50; let cap: BalanceOf = 40; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_err!( - Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), + Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + ), pallet_crowdloan::Error::::CapTooLow ); }); @@ -233,9 +269,17 @@ fn test_create_fails_if_end_is_in_the_past() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = current_block_number - 5; + let target_address: AccountOf = U256::from(42); assert_err!( - Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), + Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + ), pallet_crowdloan::Error::::CannotEndInPast ); }); @@ -250,9 +294,17 @@ fn test_create_fails_if_block_duration_is_too_short() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 11; + let target_address: AccountOf = U256::from(42); assert_err!( - Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), + Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + ), pallet_crowdloan::Error::::BlockDurationTooShort ); }); @@ -267,9 +319,17 @@ fn test_create_fails_if_block_duration_is_too_long() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 1000; + let target_address: AccountOf = U256::from(42); assert_err!( - Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), + Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + ), pallet_crowdloan::Error::::BlockDurationTooLong ); }); @@ -284,9 +344,17 @@ fn test_create_fails_if_depositor_has_insufficient_balance() { let deposit: BalanceOf = 200; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_err!( - Crowdloan::create(RuntimeOrigin::signed(depositor), deposit, cap, end), + Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + ), pallet_crowdloan::Error::::InsufficientBalance ); }); @@ -304,11 +372,14 @@ fn test_contribute_succeeds() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -423,11 +494,15 @@ fn test_contribute_fails_if_crowdloan_has_ended() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run past the end of the crowdloan @@ -456,11 +531,14 @@ fn test_contribute_fails_if_cap_has_been_raised() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -497,11 +575,14 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -529,11 +610,14 @@ fn test_contribute_fails_if_contribution_will_make_the_raised_amount_exceed_the_ let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -562,11 +646,14 @@ fn test_withdraw_succeeds() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -635,11 +722,14 @@ fn test_withdraw_succeeds_for_another_contributor() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -721,11 +811,14 @@ fn test_withdraw_fails_if_contribution_period_has_not_ended() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -768,11 +861,14 @@ fn test_withdraw_fails_if_cap_was_fully_raised() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -827,11 +923,14 @@ fn test_withdraw_fails_if_contribution_is_not_found() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -877,11 +976,14 @@ fn test_refund_succeeds() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -1051,11 +1153,14 @@ fn test_refund_fails_if_crowdloan_has_not_ended() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks @@ -1081,11 +1186,14 @@ fn test_refund_fails_if_crowdloan_has_fully_raised() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(depositor), initial_deposit, cap, - end + end, + target_address, + noop_call() )); // run some blocks From 32b61715152d4521ce69cc1835f89aab950ceebd Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 2 Apr 2025 14:23:54 +0200 Subject: [PATCH 13/70] wip finalize logic --- pallets/crowdloan/src/lib.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 68fa451e01..2f9686e226 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -386,15 +386,15 @@ pub mod pallet { pub fn finalize( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; - let crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; Self::ensure_crowdloan_succeeded(&crowdloan)?; ensure!(who == crowdloan.depositor, Error::::InvalidOrigin); - ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + // Transfer the raised amount to the target address CurrencyOf::::transfer( &Self::crowdloan_account_id(crowdloan_id), &crowdloan.target_address, @@ -402,13 +402,24 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; + // Set the current crowdloan id so the dispatched call + // can access it temporarily CurrentCrowdloanId::::put(crowdloan_id); - // crowdloan.call.dispatch(origin)?; + // Dispatch the call + let call = crowdloan.call.clone(); + call.dispatch(frame_system::RawOrigin::Signed(who).into()) + .map(|_| ()) + .map_err(|e| e.error)?; + // Clear the current crowdloan id CurrentCrowdloanId::::kill(); - Ok(().into()) + // Mark the crowdloan as finalized + crowdloan.finalized = true; + Crowdloans::::insert(crowdloan_id, &crowdloan); + + Ok(()) } } } From a7528bc7e4c72c59a4174de622d06c50a4ec3905 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 2 Apr 2025 14:40:23 +0200 Subject: [PATCH 14/70] add finalize event --- pallets/crowdloan/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 2f9686e226..4f734fdfee 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -129,6 +129,9 @@ pub mod pallet { Refunded { crowdloan_id: CrowdloanId, }, + Finalized { + crowdloan_id: CrowdloanId, + }, } #[pallet::error] @@ -419,6 +422,8 @@ pub mod pallet { crowdloan.finalized = true; Crowdloans::::insert(crowdloan_id, &crowdloan); + Self::deposit_event(Event::::Finalized { crowdloan_id }); + Ok(()) } } From 5f4ef76fc97dba1b540c419e7c75eb0d9aac2991 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 2 Apr 2025 17:42:48 +0200 Subject: [PATCH 15/70] some fix + tests for finalize --- pallets/crowdloan/src/lib.rs | 14 +- pallets/crowdloan/src/tests.rs | 273 +++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+), 5 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 4f734fdfee..f97c3322fd 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -148,10 +148,11 @@ pub mod pallet { CapExceeded, ContributionPeriodEnded, ContributionTooLow, - InvalidOrigin, + ExpectedDepositorOrigin, AlreadyFinalized, ContributionPeriodNotEnded, NoContribution, + CapNotRaised, } #[pallet::call] @@ -390,12 +391,15 @@ pub mod pallet { origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, ) -> DispatchResult { - let who = ensure_signed(origin)?; + let depositor = ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; Self::ensure_crowdloan_succeeded(&crowdloan)?; - ensure!(who == crowdloan.depositor, Error::::InvalidOrigin); + ensure!( + depositor == crowdloan.depositor, + Error::::ExpectedDepositorOrigin + ); // Transfer the raised amount to the target address CurrencyOf::::transfer( @@ -411,7 +415,7 @@ pub mod pallet { // Dispatch the call let call = crowdloan.call.clone(); - call.dispatch(frame_system::RawOrigin::Signed(who).into()) + call.dispatch(frame_system::RawOrigin::Signed(depositor).into()) .map(|_| ()) .map_err(|e| e.error)?; @@ -458,7 +462,7 @@ impl Pallet { ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); // Has raised cap - ensure!(crowdloan.raised == crowdloan.cap, Error::::CapRaised); + ensure!(crowdloan.raised == crowdloan.cap, Error::::CapNotRaised); // Has not finalized ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index b74e7141b8..4a11616452 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1149,6 +1149,7 @@ fn test_refund_fails_if_crowdloan_has_not_ended() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { + // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; @@ -1182,6 +1183,7 @@ fn test_refund_fails_if_crowdloan_has_fully_raised() { .with_balance(U256::from(2), 200) .with_balance(U256::from(3), 200) .build_and_execute(|| { + // create a crowdloan let depositor: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; @@ -1231,3 +1233,274 @@ fn test_refund_fails_if_crowdloan_has_fully_raised() { ); }); } + +#[test] +fn test_finalize_succeeds() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + )); + + // run some blocks + run_to_block(10); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // finalize the crowdloan + assert_ok!(Crowdloan::finalize( + RuntimeOrigin::signed(depositor), + crowdloan_id + )); + + // ensure the crowdloan account has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(&target_address), + 100 + ); + + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Finalized { crowdloan_id }.into() + ); + }) +} + +#[test] +fn test_finalize_fails_if_bad_origin() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + // try to finalize + assert_err!( + Crowdloan::finalize(RuntimeOrigin::none(), crowdloan_id), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_finalize_fails_if_crowdloan_does_not_exist() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let depositor: AccountOf = U256::from(1); + let crowdloan_id: CrowdloanId = 0; + + // try to finalize + assert_err!( + Crowdloan::finalize(RuntimeOrigin::signed(depositor), crowdloan_id), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[test] +fn test_finalize_fails_if_crowdloan_has_not_ended() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + )); + + // run some blocks + run_to_block(10); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks before end of contribution period + run_to_block(10); + + // try to finalize + assert_err!( + Crowdloan::finalize(RuntimeOrigin::signed(depositor), crowdloan_id), + pallet_crowdloan::Error::::ContributionPeriodNotEnded + ); + }); +} + +#[test] +fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + )); + + // run some blocks + run_to_block(10); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 49; // below cap + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // try finalize the crowdloan + assert_err!( + Crowdloan::finalize(RuntimeOrigin::signed(depositor), crowdloan_id), + pallet_crowdloan::Error::::CapNotRaised + ); + }); +} + +#[test] +fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + )); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // finalize the crowdloan + assert_ok!(Crowdloan::finalize( + RuntimeOrigin::signed(depositor), + crowdloan_id + )); + + // try finalize the crowdloan a second time + assert_err!( + Crowdloan::finalize(RuntimeOrigin::signed(depositor), crowdloan_id), + pallet_crowdloan::Error::::AlreadyFinalized + ); + }); +} + +#[test] +fn test_finalize_fails_if_not_depositor_origin() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let depositor: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(depositor), + deposit, + cap, + end, + target_address, + noop_call() + )); + + // run some blocks + run_to_block(10); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // try finalize the crowdloan + assert_err!( + Crowdloan::finalize(RuntimeOrigin::signed(contributor), crowdloan_id), + pallet_crowdloan::Error::::ExpectedDepositorOrigin + ); + }); +} From 52c6ee1e77f9fa0b130570772378acc05cb0762c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 2 Apr 2025 17:44:40 +0200 Subject: [PATCH 16/70] refacto depositor to creator --- pallets/crowdloan/src/lib.rs | 28 +++--- pallets/crowdloan/src/tests.rs | 170 ++++++++++++++++----------------- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index f97c3322fd..c52dd0f635 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -25,7 +25,7 @@ type BalanceOf = as Currency<::Acco #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct CrowdloanInfo { - pub depositor: AccountId, + pub creator: AccountId, pub deposit: Balance, pub end: BlockNumber, pub cap: Balance, @@ -109,7 +109,7 @@ pub mod pallet { pub enum Event { Created { crowdloan_id: CrowdloanId, - depositor: T::AccountId, + creator: T::AccountId, end: BlockNumberFor, cap: BalanceOf, }, @@ -148,7 +148,7 @@ pub mod pallet { CapExceeded, ContributionPeriodEnded, ContributionTooLow, - ExpectedDepositorOrigin, + ExpectedCreatorOrigin, AlreadyFinalized, ContributionPeriodNotEnded, NoContribution, @@ -167,7 +167,7 @@ pub mod pallet { target_address: T::AccountId, call: Box<::RuntimeCall>, ) -> DispatchResult { - let depositor = ensure_signed(origin)?; + let creator = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); // Ensure the deposit is at least the minimum deposit and cap is greater @@ -190,9 +190,9 @@ pub mod pallet { Error::::BlockDurationTooLong ); - // Ensure the depositor has enough balance to pay the initial deposit + // Ensure the creator has enough balance to pay the initial deposit ensure!( - CurrencyOf::::free_balance(&depositor) >= deposit, + CurrencyOf::::free_balance(&creator) >= deposit, Error::::InsufficientBalance ); @@ -202,7 +202,7 @@ pub mod pallet { Crowdloans::::insert( &crowdloan_id, CrowdloanInfo { - depositor: depositor.clone(), + creator: creator.clone(), deposit, end, cap, @@ -218,17 +218,17 @@ pub mod pallet { // Track the crowdloan account and transfer the deposit to the crowdloan account frame_system::Pallet::::inc_providers(&Self::crowdloan_account_id(crowdloan_id)); CurrencyOf::::transfer( - &depositor, + &creator, &Self::crowdloan_account_id(crowdloan_id), deposit, ExistenceRequirement::AllowDeath, )?; - Contributions::::insert(&crowdloan_id, &depositor, deposit); + Contributions::::insert(&crowdloan_id, &creator, deposit); Self::deposit_event(Event::::Created { crowdloan_id, - depositor, + creator, end, cap, }); @@ -391,14 +391,14 @@ pub mod pallet { origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, ) -> DispatchResult { - let depositor = ensure_signed(origin)?; + let creator = ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; Self::ensure_crowdloan_succeeded(&crowdloan)?; ensure!( - depositor == crowdloan.depositor, - Error::::ExpectedDepositorOrigin + creator == crowdloan.creator, + Error::::ExpectedCreatorOrigin ); // Transfer the raised amount to the target address @@ -415,7 +415,7 @@ pub mod pallet { // Dispatch the call let call = crowdloan.call.clone(); - call.dispatch(frame_system::RawOrigin::Signed(depositor).into()) + call.dispatch(frame_system::RawOrigin::Signed(creator).into()) .map(|_| ()) .map_err(|e| e.error)?; diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 4a11616452..92d925ecc0 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -129,14 +129,14 @@ fn test_create_succeeds() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -149,7 +149,7 @@ fn test_create_succeeds() { assert_eq!( pallet_crowdloan::Crowdloans::::get(crowdloan_id), Some(CrowdloanInfo { - depositor, + creator, deposit, cap, end, @@ -168,7 +168,7 @@ fn test_create_succeeds() { ); // ensure the contributions has been updated assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, depositor), + pallet_crowdloan::Contributions::::get(crowdloan_id, creator), Some(deposit) ); // ensure the event is emitted @@ -176,7 +176,7 @@ fn test_create_succeeds() { last_event(), pallet_crowdloan::Event::::Created { crowdloan_id, - depositor, + creator, end, cap, } @@ -212,7 +212,7 @@ fn test_create_fails_if_deposit_is_too_low() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 20; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; @@ -220,7 +220,7 @@ fn test_create_fails_if_deposit_is_too_low() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -237,7 +237,7 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 40; let end: BlockNumberFor = 50; @@ -245,7 +245,7 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -265,7 +265,7 @@ fn test_create_fails_if_end_is_in_the_past() { .with_block_number(current_block_number) .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = current_block_number - 5; @@ -273,7 +273,7 @@ fn test_create_fails_if_end_is_in_the_past() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -290,7 +290,7 @@ fn test_create_fails_if_block_duration_is_too_short() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 11; @@ -298,7 +298,7 @@ fn test_create_fails_if_block_duration_is_too_short() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -315,7 +315,7 @@ fn test_create_fails_if_block_duration_is_too_long() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 1000; @@ -323,7 +323,7 @@ fn test_create_fails_if_block_duration_is_too_long() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -336,11 +336,11 @@ fn test_create_fails_if_block_duration_is_too_long() { } #[test] -fn test_create_fails_if_depositor_has_insufficient_balance() { +fn test_create_fails_if_creator_has_insufficient_balance() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 200; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; @@ -348,7 +348,7 @@ fn test_create_fails_if_depositor_has_insufficient_balance() { assert_err!( Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -368,13 +368,13 @@ fn test_contribute_succeeds() { .with_balance(U256::from(3), 200) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -385,11 +385,11 @@ fn test_contribute_succeeds() { // run some blocks run_to_block(10); - // first contribution to the crowdloan from depositor + // first contribution to the crowdloan from creator let crowdloan_id: CrowdloanId = 0; let amount: BalanceOf = 50; assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), crowdloan_id, amount )); @@ -397,13 +397,13 @@ fn test_contribute_succeeds() { last_event(), pallet_crowdloan::Event::::Contributed { crowdloan_id, - contributor: depositor, + contributor: creator, amount, } .into() ); assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, depositor), + pallet_crowdloan::Contributions::::get(crowdloan_id, creator), Some(100) ); @@ -490,14 +490,14 @@ fn test_contribute_fails_if_crowdloan_has_ended() { .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -527,13 +527,13 @@ fn test_contribute_fails_if_cap_has_been_raised() { .with_balance(U256::from(3), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -571,13 +571,13 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -606,13 +606,13 @@ fn test_contribute_fails_if_contribution_will_make_the_raised_amount_exceed_the_ .with_balance(U256::from(2), 1000) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -642,13 +642,13 @@ fn test_withdraw_succeeds() { .with_balance(U256::from(3), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -672,15 +672,15 @@ fn test_withdraw_succeeds() { // run some more blocks past the end of the contribution period run_to_block(60); - // withdraw from depositor + // withdraw from creator assert_ok!(Crowdloan::withdraw( - RuntimeOrigin::signed(depositor), - depositor, + RuntimeOrigin::signed(creator), + creator, crowdloan_id )); - // ensure the depositor has the correct amount + // ensure the creator has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(&depositor), + pallet_balances::Pallet::::free_balance(&creator), 100 ); @@ -718,13 +718,13 @@ fn test_withdraw_succeeds_for_another_contributor() { .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -748,16 +748,16 @@ fn test_withdraw_succeeds_for_another_contributor() { // run some more blocks past the end of the contribution period run_to_block(60); - // withdraw for depositor as a contributor + // withdraw for creator as a contributor assert_ok!(Crowdloan::withdraw( RuntimeOrigin::signed(contributor), - depositor, + creator, crowdloan_id )); - // ensure the depositor has the correct amount + // ensure the creator has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(&depositor), + pallet_balances::Pallet::::free_balance(&creator), 100 ); @@ -807,13 +807,13 @@ fn test_withdraw_fails_if_contribution_period_has_not_ended() { .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -857,13 +857,13 @@ fn test_withdraw_fails_if_cap_was_fully_raised() { .with_balance(U256::from(3), 200) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -919,13 +919,13 @@ fn test_withdraw_fails_if_contribution_is_not_found() { .with_balance(U256::from(3), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -972,13 +972,13 @@ fn test_refund_succeeds() { .with_balance(U256::from(5), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -1031,7 +1031,7 @@ fn test_refund_succeeds() { // first round of refund assert_ok!(Crowdloan::refund( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), crowdloan_id )); @@ -1058,7 +1058,7 @@ fn test_refund_succeeds() { // second round of refund assert_ok!(Crowdloan::refund( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), crowdloan_id )); @@ -1083,7 +1083,7 @@ fn test_refund_succeeds() { // third round of refund assert_ok!(Crowdloan::refund( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), crowdloan_id )); @@ -1103,9 +1103,9 @@ fn test_refund_succeeds() { pallet_crowdloan::Event::::Refunded { crowdloan_id }.into() ); - // ensure depositor has the correct amount + // ensure creator has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(&depositor), + pallet_balances::Pallet::::free_balance(&creator), 100 ); @@ -1134,11 +1134,11 @@ fn test_refund_fails_if_crowdloan_does_not_exist() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let crowdloan_id: CrowdloanId = 0; assert_err!( - Crowdloan::refund(RuntimeOrigin::signed(depositor), crowdloan_id), + Crowdloan::refund(RuntimeOrigin::signed(creator), crowdloan_id), pallet_crowdloan::Error::::InvalidCrowdloanId ); }); @@ -1150,13 +1150,13 @@ fn test_refund_fails_if_crowdloan_has_not_ended() { .with_balance(U256::from(1), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -1170,7 +1170,7 @@ fn test_refund_fails_if_crowdloan_has_not_ended() { // try to refund let crowdloan_id: CrowdloanId = 0; assert_err!( - Crowdloan::refund(RuntimeOrigin::signed(depositor), crowdloan_id), + Crowdloan::refund(RuntimeOrigin::signed(creator), crowdloan_id), pallet_crowdloan::Error::::ContributionPeriodNotEnded ); }); @@ -1184,13 +1184,13 @@ fn test_refund_fails_if_crowdloan_has_fully_raised() { .with_balance(U256::from(3), 200) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), initial_deposit, cap, end, @@ -1228,7 +1228,7 @@ fn test_refund_fails_if_crowdloan_has_fully_raised() { // try to refund assert_err!( - Crowdloan::refund(RuntimeOrigin::signed(depositor), crowdloan_id), + Crowdloan::refund(RuntimeOrigin::signed(creator), crowdloan_id), pallet_crowdloan::Error::::CapRaised ); }); @@ -1241,13 +1241,13 @@ fn test_finalize_succeeds() { .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -1273,7 +1273,7 @@ fn test_finalize_succeeds() { // finalize the crowdloan assert_ok!(Crowdloan::finalize( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), crowdloan_id )); @@ -1311,12 +1311,12 @@ fn test_finalize_fails_if_crowdloan_does_not_exist() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let crowdloan_id: CrowdloanId = 0; // try to finalize assert_err!( - Crowdloan::finalize(RuntimeOrigin::signed(depositor), crowdloan_id), + Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), pallet_crowdloan::Error::::InvalidCrowdloanId ); }); @@ -1329,13 +1329,13 @@ fn test_finalize_fails_if_crowdloan_has_not_ended() { .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -1361,7 +1361,7 @@ fn test_finalize_fails_if_crowdloan_has_not_ended() { // try to finalize assert_err!( - Crowdloan::finalize(RuntimeOrigin::signed(depositor), crowdloan_id), + Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), pallet_crowdloan::Error::::ContributionPeriodNotEnded ); }); @@ -1374,13 +1374,13 @@ fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -1406,7 +1406,7 @@ fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { // try finalize the crowdloan assert_err!( - Crowdloan::finalize(RuntimeOrigin::signed(depositor), crowdloan_id), + Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), pallet_crowdloan::Error::::CapNotRaised ); }); @@ -1419,13 +1419,13 @@ fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -1448,32 +1448,32 @@ fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { // finalize the crowdloan assert_ok!(Crowdloan::finalize( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), crowdloan_id )); // try finalize the crowdloan a second time assert_err!( - Crowdloan::finalize(RuntimeOrigin::signed(depositor), crowdloan_id), + Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), pallet_crowdloan::Error::::AlreadyFinalized ); }); } #[test] -fn test_finalize_fails_if_not_depositor_origin() { +fn test_finalize_fails_if_not_creator_origin() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) .build_and_execute(|| { // create a crowdloan - let depositor: AccountOf = U256::from(1); + let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(depositor), + RuntimeOrigin::signed(creator), deposit, cap, end, @@ -1500,7 +1500,7 @@ fn test_finalize_fails_if_not_depositor_origin() { // try finalize the crowdloan assert_err!( Crowdloan::finalize(RuntimeOrigin::signed(contributor), crowdloan_id), - pallet_crowdloan::Error::::ExpectedDepositorOrigin + pallet_crowdloan::Error::::ExpectedCreatorOrigin ); }); } From 857615abd74101647e8310933c571c80327443a2 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 2 Apr 2025 17:51:00 +0200 Subject: [PATCH 17/70] add missing test for insufficient balance from contributor --- pallets/crowdloan/src/lib.rs | 3 ++ pallets/crowdloan/src/tests.rs | 50 ++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index c52dd0f635..1484e52c65 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -257,6 +257,7 @@ pub mod pallet { amount >= T::MinimumContribution::get(), Error::::ContributionTooLow ); + // Ensure contribution does not overflow the actual raised amount // and it does not exceed the cap crowdloan.raised = crowdloan @@ -264,11 +265,13 @@ pub mod pallet { .checked_add(&amount) .ok_or(Error::::Overflow)?; ensure!(crowdloan.raised <= crowdloan.cap, Error::::CapExceeded); + // Ensure contribution does not overflow the contributor's total contributions let contribution = Contributions::::get(&crowdloan_id, &contributor) .unwrap_or(Zero::zero()) .checked_add(&amount) .ok_or(Error::::Overflow)?; + // Ensure contributor has enough balance to pay ensure!( CurrencyOf::::free_balance(&contributor) >= amount, diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 92d925ecc0..aaf4a5da3b 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -634,6 +634,41 @@ fn test_contribute_fails_if_contribution_will_make_the_raised_amount_exceed_the_ }); } +#[test] +fn test_contribute_fails_if_contributor_has_insufficient_balance() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 50) + .build_and_execute(|| { + // create a crowdloan + let creator: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + initial_deposit, + cap, + end, + target_address, + noop_call() + )); + + // run some blocks + run_to_block(10); + + // contribute to the crowdloan + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 100; + assert_err!( + Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), + pallet_crowdloan::Error::::InsufficientBalance + ); + }); +} + #[test] fn test_withdraw_succeeds() { TestState::default() @@ -679,10 +714,7 @@ fn test_withdraw_succeeds() { crowdloan_id )); // ensure the creator has the correct amount - assert_eq!( - pallet_balances::Pallet::::free_balance(&creator), - 100 - ); + assert_eq!(pallet_balances::Pallet::::free_balance(&creator), 100); // withdraw from contributor assert_ok!(Crowdloan::withdraw( @@ -756,10 +788,7 @@ fn test_withdraw_succeeds_for_another_contributor() { )); // ensure the creator has the correct amount - assert_eq!( - pallet_balances::Pallet::::free_balance(&creator), - 100 - ); + assert_eq!(pallet_balances::Pallet::::free_balance(&creator), 100); // ensure the contributor has the correct amount assert_eq!( @@ -1104,10 +1133,7 @@ fn test_refund_succeeds() { ); // ensure creator has the correct amount - assert_eq!( - pallet_balances::Pallet::::free_balance(&creator), - 100 - ); + assert_eq!(pallet_balances::Pallet::::free_balance(&creator), 100); // ensure each contributor has been refunded assert_eq!( From 87bd5af2ee3d78cc3d98d099f4d22cd6b684b3b1 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 2 Apr 2025 17:55:55 +0200 Subject: [PATCH 18/70] add missing tests on bad origin (root/none) --- pallets/crowdloan/src/tests.rs | 70 +++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index aaf4a5da3b..69ac476606 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -204,6 +204,18 @@ fn test_create_fails_if_bad_origin() { ), DispatchError::BadOrigin ); + + assert_err!( + Crowdloan::create( + RuntimeOrigin::root(), + deposit, + cap, + end, + target_address, + noop_call() + ), + DispatchError::BadOrigin + ); }); } @@ -467,6 +479,24 @@ fn test_contribute_succeeds() { }); } +#[test] +fn test_contribute_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + let amount: BalanceOf = 100; + + assert_err!( + Crowdloan::contribute(RuntimeOrigin::none(), crowdloan_id, amount), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::contribute(RuntimeOrigin::root(), crowdloan_id, amount), + DispatchError::BadOrigin + ); + }); +} + #[test] fn test_contribute_fails_if_crowdloan_does_not_exist() { TestState::default() @@ -743,6 +773,23 @@ fn test_withdraw_succeeds() { }); } +#[test] +fn test_withdraw_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::withdraw(RuntimeOrigin::none(), U256::from(1), crowdloan_id), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::withdraw(RuntimeOrigin::root(), U256::from(1), crowdloan_id), + DispatchError::BadOrigin + ); + }); +} + #[test] fn test_withdraw_succeeds_for_another_contributor() { TestState::default() @@ -1155,6 +1202,23 @@ fn test_refund_succeeds() { }) } +#[test] +fn test_refund_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::refund(RuntimeOrigin::none(), crowdloan_id), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::refund(RuntimeOrigin::root(), crowdloan_id), + DispatchError::BadOrigin + ); + }); +} + #[test] fn test_refund_fails_if_crowdloan_does_not_exist() { TestState::default() @@ -1324,11 +1388,15 @@ fn test_finalize_fails_if_bad_origin() { .build_and_execute(|| { let crowdloan_id: CrowdloanId = 0; - // try to finalize assert_err!( Crowdloan::finalize(RuntimeOrigin::none(), crowdloan_id), DispatchError::BadOrigin ); + + assert_err!( + Crowdloan::finalize(RuntimeOrigin::root(), crowdloan_id), + DispatchError::BadOrigin + ); }); } From 1d2b3bb91372a347da7b9a891d10e54563347d04 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 3 Apr 2025 10:30:31 +0200 Subject: [PATCH 19/70] make it such we can't exceed the cap when contributing --- pallets/crowdloan/src/lib.rs | 17 ++++- pallets/crowdloan/src/tests.rs | 120 +++++++++++++++++++++++---------- 2 files changed, 99 insertions(+), 38 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 1484e52c65..33345610a2 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -153,6 +153,7 @@ pub mod pallet { ContributionPeriodNotEnded, NoContribution, CapNotRaised, + Underflow, } #[pallet::call] @@ -180,7 +181,7 @@ pub mod pallet { // Ensure the end block is after the current block and the duration is // between the minimum and maximum block duration ensure!(now < end, Error::::CannotEndInPast); - let block_duration = end.checked_sub(&now).expect("checked end after now; qed"); + let block_duration = end.checked_sub(&now).ok_or(Error::::Underflow)?; ensure!( block_duration >= T::MinimumBlockDuration::get(), Error::::BlockDurationTooShort @@ -258,13 +259,24 @@ pub mod pallet { Error::::ContributionTooLow ); + ensure!(crowdloan.raised <= crowdloan.cap, Error::::CapExceeded); + // Ensure contribution does not overflow the actual raised amount // and it does not exceed the cap + let left_to_raise = crowdloan + .cap + .checked_sub(&crowdloan.raised) + .ok_or(Error::::Underflow)?; + + // If the contribution would raise the amount above the cap, + // set the contribution to the amount that is left to be raised + let amount = amount.min(left_to_raise); + + // Ensure contribution does not overflow the actual raised amount crowdloan.raised = crowdloan .raised .checked_add(&amount) .ok_or(Error::::Overflow)?; - ensure!(crowdloan.raised <= crowdloan.cap, Error::::CapExceeded); // Ensure contribution does not overflow the contributor's total contributions let contribution = Contributions::::get(&crowdloan_id, &contributor) @@ -286,7 +298,6 @@ pub mod pallet { )?; Contributions::::insert(&crowdloan_id, &contributor, contribution); - Crowdloans::::insert(crowdloan_id, &crowdloan); Self::deposit_event(Event::::Contributed { diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 69ac476606..bfcf11641a 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -479,6 +479,91 @@ fn test_contribute_succeeds() { }); } +#[test] +fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_the_cap() { + TestState::default() + .with_balance(U256::from(1), 200) + .with_balance(U256::from(2), 500) + .with_balance(U256::from(3), 200) + .build_and_execute(|| { + // create a crowdloan + let creator: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + initial_deposit, + cap, + end, + target_address, + noop_call() + )); + + // run some blocks + run_to_block(10); + + // first contribution to the crowdloan from creator + let crowdloan_id: CrowdloanId = 0; + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(creator), + crowdloan_id, + amount + )); + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Contributed { + crowdloan_id, + contributor: creator, + amount, + } + .into() + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, creator), + Some(100) + ); + + // second contribution to the crowdloan above the cap + let contributor1: AccountOf = U256::from(2); + let amount: BalanceOf = 300; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor1), + crowdloan_id, + amount + )); + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Contributed { + crowdloan_id, + contributor: contributor1, + amount: 200, // the amount is capped at the cap + } + .into() + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, contributor1), + Some(200) + ); + + // ensure the contributions are present in the crowdloan account up to the cap + let crowdloan_account_id: AccountOf = + pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); + assert_eq!( + pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + 300 + ); + + // ensure the crowdloan raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 300) + ); + }); +} + #[test] fn test_contribute_fails_if_bad_origin() { TestState::default().build_and_execute(|| { @@ -629,41 +714,6 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { }); } -#[test] -fn test_contribute_fails_if_contribution_will_make_the_raised_amount_exceed_the_cap() { - TestState::default() - .with_balance(U256::from(1), 100) - .with_balance(U256::from(2), 1000) - .build_and_execute(|| { - // create a crowdloan - let creator: AccountOf = U256::from(1); - let initial_deposit: BalanceOf = 50; - let cap: BalanceOf = 300; - let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); - assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(creator), - initial_deposit, - cap, - end, - target_address, - noop_call() - )); - - // run some blocks - run_to_block(10); - - // contribute to the crowdloan - let contributor: AccountOf = U256::from(2); - let crowdloan_id: CrowdloanId = 0; - let amount: BalanceOf = 300; - assert_err!( - Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), - pallet_crowdloan::Error::::CapExceeded - ); - }); -} - #[test] fn test_contribute_fails_if_contributor_has_insufficient_balance() { TestState::default() From 58fb6a28bbe3e3dcae2a2081f7b5bc06f93315eb Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 3 Apr 2025 10:38:51 +0200 Subject: [PATCH 20/70] fix rust --- pallets/crowdloan/src/lib.rs | 20 +++++++++-------- pallets/crowdloan/src/tests.rs | 40 +++++++++++++++++----------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 33345610a2..15acaa3b59 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -15,6 +15,7 @@ use frame_system::pallet_prelude::*; use scale_info::TypeInfo; pub use pallet::*; +use subtensor_macros::freeze_struct; type CrowdloanId = u32; @@ -23,6 +24,7 @@ mod tests; type CurrencyOf = ::Currency; type BalanceOf = as Currency<::AccountId>>::Balance; +#[freeze_struct("f7387ea6541ffbae")] #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct CrowdloanInfo { pub creator: AccountId, @@ -201,7 +203,7 @@ pub mod pallet { let next_crowdloan_id = crowdloan_id.checked_add(1).ok_or(Error::::Overflow)?; Crowdloans::::insert( - &crowdloan_id, + crowdloan_id, CrowdloanInfo { creator: creator.clone(), deposit, @@ -225,7 +227,7 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; - Contributions::::insert(&crowdloan_id, &creator, deposit); + Contributions::::insert(crowdloan_id, &creator, deposit); Self::deposit_event(Event::::Created { crowdloan_id, @@ -279,7 +281,7 @@ pub mod pallet { .ok_or(Error::::Overflow)?; // Ensure contribution does not overflow the contributor's total contributions - let contribution = Contributions::::get(&crowdloan_id, &contributor) + let contribution = Contributions::::get(crowdloan_id, &contributor) .unwrap_or(Zero::zero()) .checked_add(&amount) .ok_or(Error::::Overflow)?; @@ -297,7 +299,7 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; - Contributions::::insert(&crowdloan_id, &contributor, contribution); + Contributions::::insert(crowdloan_id, &contributor, contribution); Crowdloans::::insert(crowdloan_id, &crowdloan); Self::deposit_event(Event::::Contributed { @@ -321,8 +323,8 @@ pub mod pallet { Self::ensure_crowdloan_failed(&crowdloan)?; // Ensure contributor has balance left in the crowdloan account - let amount = Contributions::::get(&crowdloan_id, &contributor) - .unwrap_or_else(|| Zero::zero()); + let amount = + Contributions::::get(crowdloan_id, &contributor).unwrap_or_else(Zero::zero); ensure!(amount > Zero::zero(), Error::::NoContribution); CurrencyOf::::transfer( @@ -334,7 +336,7 @@ pub mod pallet { // Remove the contribution from the contributions map and update // refunds so far - Contributions::::remove(&crowdloan_id, &contributor); + Contributions::::remove(crowdloan_id, &contributor); crowdloan.raised = crowdloan.raised.saturating_sub(amount); Crowdloans::::insert(crowdloan_id, &crowdloan); @@ -380,14 +382,14 @@ pub mod pallet { refunded_contributors.push(contributor); crowdloan.raised = crowdloan.raised.saturating_sub(amount); - refund_count += 1; + refund_count = refund_count.checked_add(1).ok_or(Error::::Overflow)?; } Crowdloans::::insert(crowdloan_id, &crowdloan); // Clear refunded contributors for contributor in refunded_contributors { - Contributions::::remove(&crowdloan_id, &contributor); + Contributions::::remove(crowdloan_id, &contributor); } if all_refunded { diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index bfcf11641a..94d20030a2 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1,4 +1,5 @@ #![cfg(test)] +#![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] use frame_support::{ PalletId, assert_err, assert_ok, derive_impl, parameter_types, @@ -81,7 +82,7 @@ impl TestState { self } - fn build_and_execute(self, test: impl FnOnce() -> ()) { + fn build_and_execute(self, test: impl FnOnce()) { let mut t = frame_system::GenesisConfig::::default() .build_storage() .unwrap(); @@ -92,7 +93,6 @@ impl TestState { .iter() .map(|(who, balance)| (*who, *balance)) .collect::>(), - ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -161,7 +161,7 @@ fn test_create_succeeds() { ); // ensure the crowdloan account has the deposit assert_eq!( - Balances::free_balance(&pallet_crowdloan::Pallet::::crowdloan_account_id( + Balances::free_balance(pallet_crowdloan::Pallet::::crowdloan_account_id( crowdloan_id )), deposit @@ -467,7 +467,7 @@ fn test_contribute_succeeds() { let crowdloan_account_id: AccountOf = pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); assert_eq!( - pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + pallet_balances::Pallet::::free_balance(crowdloan_account_id), 250 ); @@ -552,7 +552,7 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t let crowdloan_account_id: AccountOf = pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); assert_eq!( - pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + pallet_balances::Pallet::::free_balance(crowdloan_account_id), 300 ); @@ -794,7 +794,7 @@ fn test_withdraw_succeeds() { crowdloan_id )); // ensure the creator has the correct amount - assert_eq!(pallet_balances::Pallet::::free_balance(&creator), 100); + assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); // withdraw from contributor assert_ok!(Crowdloan::withdraw( @@ -804,7 +804,7 @@ fn test_withdraw_succeeds() { )); // ensure the contributor has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(&contributor), + pallet_balances::Pallet::::free_balance(contributor), 100 ); @@ -812,7 +812,7 @@ fn test_withdraw_succeeds() { let crowdloan_account_id: AccountOf = pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); assert_eq!( - pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + pallet_balances::Pallet::::free_balance(crowdloan_account_id), 0 ); // ensure the crowdloan raised amount is updated correctly @@ -885,11 +885,11 @@ fn test_withdraw_succeeds_for_another_contributor() { )); // ensure the creator has the correct amount - assert_eq!(pallet_balances::Pallet::::free_balance(&creator), 100); + assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); // ensure the contributor has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(&contributor), + pallet_balances::Pallet::::free_balance(contributor), 0 ); @@ -897,7 +897,7 @@ fn test_withdraw_succeeds_for_another_contributor() { let crowdloan_account_id: AccountOf = pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); assert_eq!( - pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + pallet_balances::Pallet::::free_balance(crowdloan_account_id), 100 ); @@ -1165,7 +1165,7 @@ fn test_refund_succeeds() { let crowdloan_account_id: AccountOf = pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); assert_eq!( - pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + pallet_balances::Pallet::::free_balance(crowdloan_account_id), 150 // 2 contributors have been refunded so far ); // ensure raised amount is updated correctly @@ -1190,7 +1190,7 @@ fn test_refund_succeeds() { // ensure the crowdloan account has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + pallet_balances::Pallet::::free_balance(crowdloan_account_id), 50 ); // ensure raised amount is updated correctly @@ -1215,7 +1215,7 @@ fn test_refund_succeeds() { // ensure the crowdloan account has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(&crowdloan_account_id), + pallet_balances::Pallet::::free_balance(crowdloan_account_id), 0 ); // ensure the raised amount is updated correctly @@ -1230,23 +1230,23 @@ fn test_refund_succeeds() { ); // ensure creator has the correct amount - assert_eq!(pallet_balances::Pallet::::free_balance(&creator), 100); + assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); // ensure each contributor has been refunded assert_eq!( - pallet_balances::Pallet::::free_balance(&contributor), + pallet_balances::Pallet::::free_balance(contributor), 100 ); assert_eq!( - pallet_balances::Pallet::::free_balance(&contributor2), + pallet_balances::Pallet::::free_balance(contributor2), 100 ); assert_eq!( - pallet_balances::Pallet::::free_balance(&contributor3), + pallet_balances::Pallet::::free_balance(contributor3), 100 ); assert_eq!( - pallet_balances::Pallet::::free_balance(&contributor4), + pallet_balances::Pallet::::free_balance(contributor4), 100 ); }) @@ -1419,7 +1419,7 @@ fn test_finalize_succeeds() { // ensure the crowdloan account has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(&target_address), + pallet_balances::Pallet::::free_balance(target_address), 100 ); From 0655c43d7a7594eebdb4da3ed895fb73dcc3b4e0 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 3 Apr 2025 11:16:49 +0200 Subject: [PATCH 21/70] added test pallet to ensure crowdloan id is available in the dispatched call --- pallets/crowdloan/src/tests.rs | 114 +++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 5 deletions(-) diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 94d20030a2..5d36ca5f06 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -20,6 +20,7 @@ frame_support::construct_runtime!( System: frame_system = 1, Balances: pallet_balances = 2, Crowdloan: pallet_crowdloan = 3, + TestPallet: pallet_test = 4, } ); @@ -57,6 +58,56 @@ impl pallet_crowdloan::Config for Test { type RefundContributorsLimit = RefundContributorsLimit; } +// A test pallet used to test some behavior of the crowdloan pallet +#[allow(unused)] +#[frame_support::pallet(dev_mode)] +mod pallet_test { + use super::*; + use frame_support::{ + dispatch::DispatchResult, + pallet_prelude::{OptionQuery, StorageValue}, + }; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_crowdloan::Config {} + + #[pallet::error] + pub enum Error { + SomeError, + MissingCurrentCrowdloanId, + } + + #[pallet::storage] + pub type PassedCrowdloanId = StorageValue<_, CrowdloanId, OptionQuery>; + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + pub fn noop(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + #[pallet::call_index(1)] + pub fn set_passed_crowdloan_id(origin: OriginFor) -> DispatchResult { + let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() + .ok_or(Error::::MissingCurrentCrowdloanId)?; + PassedCrowdloanId::::put(crowdloan_id); + Ok(()) + } + + #[pallet::call_index(2)] + pub fn failing_extrinsic(origin: OriginFor) -> DispatchResult { + Err(Error::::SomeError.into()) + } + } +} + +impl pallet_test::Config for Test {} + pub(crate) struct TestState { block_number: BlockNumberFor, balances: Vec<(AccountOf, BalanceOf)>, @@ -119,9 +170,7 @@ pub(crate) fn run_to_block(n: u64) { } fn noop_call() -> Box { - Box::new(RuntimeCall::System(frame_system::Call::::remark { - remark: vec![], - })) + Box::new(RuntimeCall::TestPallet(pallet_test::Call::::noop {})) } #[test] @@ -1392,7 +1441,9 @@ fn test_finalize_succeeds() { cap, end, target_address, - noop_call() + Box::new(RuntimeCall::TestPallet( + pallet_test::Call::::set_passed_crowdloan_id {} + )) )); // run some blocks @@ -1417,7 +1468,7 @@ fn test_finalize_succeeds() { crowdloan_id )); - // ensure the crowdloan account has the correct amount + // ensure the target address has received the funds assert_eq!( pallet_balances::Pallet::::free_balance(target_address), 100 @@ -1428,6 +1479,12 @@ fn test_finalize_succeeds() { last_event(), pallet_crowdloan::Event::::Finalized { crowdloan_id }.into() ); + + // ensure the current crowdloan id was accessible from the dispatched call + assert_eq!( + pallet_test::PassedCrowdloanId::::get(), + Some(crowdloan_id) + ); }) } @@ -1648,3 +1705,50 @@ fn test_finalize_fails_if_not_creator_origin() { ); }); } + +#[test] +fn test_finalize_fails_if_call_fails() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + // create a crowdloan + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + cap, + end, + target_address, + Box::new(RuntimeCall::TestPallet( + pallet_test::Call::::failing_extrinsic {} + )) + )); + + // run some blocks + run_to_block(10); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // try finalize the crowdloan + assert_err!( + Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_test::Error::::SomeError + ); + }); +} From 46024f18f2f0fb04c3768286e2879547355d58d6 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 3 Apr 2025 12:19:04 +0200 Subject: [PATCH 22/70] added readme and docs to crowdloan pallet --- pallets/crowdloan/README.md | 21 ++++++ pallets/crowdloan/src/lib.rs | 132 ++++++++++++++++++++++++++++++----- 2 files changed, 137 insertions(+), 16 deletions(-) create mode 100644 pallets/crowdloan/README.md diff --git a/pallets/crowdloan/README.md b/pallets/crowdloan/README.md new file mode 100644 index 0000000000..172f354d23 --- /dev/null +++ b/pallets/crowdloan/README.md @@ -0,0 +1,21 @@ +# Crowdloan Pallet +A pallet allowing to create and manage generic crowdloans around a transfer of funds and a arbitrary call. + +A user of this pallet can create a crowdloan by providing a deposit, a cap, an end block, a target address and a call. + +Users will be able to contribute to the crowdloan by providing funds to the crowdloan they chose to contribute to. + +Once the crowdloan is finalized, the funds will be transferred to the target address and the call will be dispatched with the current crowdloan id as a temporary storage item. + +In case the crowdloan fails to raise the cap, the initial deposit will be returned to the creator and contributions will be returned to the contributors. + +## Overview + +## Interface + +### Dispatchable Functions + +[`Call`]: ./enum.Call.html +[`Config`]: ./trait.Config.html + +License: Apache-2.0 diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 15acaa3b59..5a5aa02fd6 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -1,5 +1,11 @@ #![cfg_attr(not(feature = "std"), no_std)] +//! # Crowdloan Pallet +//! +//! A pallet allowing users to create generic crowdloans and contribute to them, +//! the raised funds are then dispatched to a target address and an extrinsic +//! is dispatched, making it reusable for any crowdloan type. + use codec::{Decode, Encode}; use frame_support::pallet_prelude::*; use frame_support::{ @@ -24,16 +30,25 @@ mod tests; type CurrencyOf = ::Currency; type BalanceOf = as Currency<::AccountId>>::Balance; -#[freeze_struct("f7387ea6541ffbae")] +/// A struct containing the information about a crowdloan. +#[freeze_struct("175f314cf5c0cebc")] #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct CrowdloanInfo { + /// The creator of the crowdloan. pub creator: AccountId, + /// The initial deposit of the crowdloan from the creator. pub deposit: Balance, + /// The end block of the crowdloan. pub end: BlockNumber, + /// The cap to raise. pub cap: Balance, + /// The amount raised so far. pub raised: Balance, + /// The target address to transfer the raised funds to. pub target_address: AccountId, + /// The call to dispatch when the crowdloan is finalized. pub call: Box, + /// Whether the crowdloan has been finalized. pub finalized: bool, } @@ -53,10 +68,13 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(_); + /// Configuration trait. #[pallet::config] pub trait Config: frame_system::Config { + /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The overarching call type. type RuntimeCall: Parameter + Dispatchable + GetDispatchInfo @@ -64,34 +82,44 @@ pub mod pallet { + IsSubType> + IsType<::RuntimeCall>; + /// The currency mechanism. type Currency: ReservableCurrency; + /// The pallet id that will be used to derive crowdloan account ids. #[pallet::constant] type PalletId: Get; + /// The minimum deposit required to create a crowdloan. #[pallet::constant] type MinimumDeposit: Get>; + /// The minimum contribution required to contribute to a crowdloan. #[pallet::constant] type MinimumContribution: Get>; + /// The minimum block duration for a crowdloan. #[pallet::constant] type MinimumBlockDuration: Get>; + /// The maximum block duration for a crowdloan. #[pallet::constant] type MaximumBlockDuration: Get>; + /// The maximum number of contributors that can be refunded in a single refund. #[pallet::constant] type RefundContributorsLimit: Get; } + /// A map of crowdloan ids to their information. #[pallet::storage] pub type Crowdloans = StorageMap<_, Identity, CrowdloanId, CrowdloanInfoOf, OptionQuery>; + /// The next incrementing crowdloan id. #[pallet::storage] pub type NextCrowdloanId = StorageValue<_, CrowdloanId, ValueQuery, ConstU32<0>>; + /// A map of crowdloan ids to their contributors and their contributions. #[pallet::storage] pub type Contributions = StorageDoubleMap< _, @@ -103,64 +131,95 @@ pub mod pallet { OptionQuery, >; + /// The current crowdloan id that will be set during the finalize call, making it + /// temporarily accessible to the dispatched call. #[pallet::storage] pub type CurrentCrowdloanId = StorageValue<_, CrowdloanId, OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// A crowdloan was created. Created { crowdloan_id: CrowdloanId, creator: T::AccountId, end: BlockNumberFor, cap: BalanceOf, }, + /// A contribution was made to an active crowdloan. Contributed { crowdloan_id: CrowdloanId, contributor: T::AccountId, amount: BalanceOf, }, + /// A contribution was withdrawn from a failed crowdloan. Withdrew { crowdloan_id: CrowdloanId, contributor: T::AccountId, amount: BalanceOf, }, - PartiallyRefunded { - crowdloan_id: CrowdloanId, - }, - Refunded { - crowdloan_id: CrowdloanId, - }, - Finalized { - crowdloan_id: CrowdloanId, - }, + /// A refund was partially processed for a failed crowdloan. + PartiallyRefunded { crowdloan_id: CrowdloanId }, + /// A refund was fully processed for a failed crowdloan. + Refunded { crowdloan_id: CrowdloanId }, + /// A crowdloan was finalized, funds were transferred and the call was dispatched. + Finalized { crowdloan_id: CrowdloanId }, } #[pallet::error] pub enum Error { + /// The crowdloan initial deposit is too low. DepositTooLow, + /// The crowdloan cap is too low. CapTooLow, + /// The crowdloan cannot end in the past. CannotEndInPast, + /// The crowdloan block duration is too short. BlockDurationTooShort, + /// The block duration is too long. BlockDurationTooLong, + /// The account does not have enough balance to pay for the initial deposit/contribution. InsufficientBalance, + /// An overflow occurred. Overflow, + /// The crowdloan id is invalid. InvalidCrowdloanId, + /// The crowdloan cap has been fully raised. CapRaised, - CapExceeded, + /// The contribution period has ended. ContributionPeriodEnded, + /// The contribution is too low. ContributionTooLow, + /// The origin is not from the creator of the crowdloan. ExpectedCreatorOrigin, + /// The crowdloan has already been finalized. AlreadyFinalized, + /// The crowdloan contribution period has not ended yet. ContributionPeriodNotEnded, + /// The contributor has no contribution for this crowdloan. NoContribution, + /// The crowdloan cap has not been raised. CapNotRaised, + /// An underflow occurred. Underflow, } #[pallet::call] impl Pallet { - /// Create a crowdloan + /// Create a crowdloan that will raise funds up to a maximum cap and if successful, + /// will transfer funds to the target address and dispatch a call. + /// + /// The initial deposit will be transfered to the crowdloan account and will be refunded + /// in case the crowdloan fails to raise the cap. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `deposit`: The initial deposit from the creator. + /// - `cap`: The maximum amount of funds that can be raised. + /// - `end`: The block number at which the crowdloan will end. + /// - `target_address`: The address to transfer the raised funds to. + /// - `call`: The call to dispatch when the crowdloan is finalized. #[pallet::call_index(0)] pub fn create( origin: OriginFor, @@ -239,7 +298,17 @@ pub mod pallet { Ok(()) } - /// Contribute to a crowdloan + /// Contribute to an active crowdloan. + /// + /// The contribution will be transfered to the crowdloan account and will be refunded + /// if the crowdloan fails to raise the cap. If the contribution would raise the amount above the cap, + /// the contribution will be set to the amount that is left to be raised. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `crowdloan_id`: The id of the crowdloan to contribute to. + /// - `amount`: The amount to contribute. #[pallet::call_index(1)] pub fn contribute( origin: OriginFor, @@ -261,8 +330,6 @@ pub mod pallet { Error::::ContributionTooLow ); - ensure!(crowdloan.raised <= crowdloan.cap, Error::::CapExceeded); - // Ensure contribution does not overflow the actual raised amount // and it does not exceed the cap let left_to_raise = crowdloan @@ -311,6 +378,16 @@ pub mod pallet { Ok(()) } + /// Withdraw a contribution from a failed crowdloan. + /// + /// The origin doesn't needs to be the contributor, it can be any account, + /// making it possible for someone to trigger a refund for a contributor. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `contributor`: The contributor to withdraw from. + /// - `crowdloan_id`: The id of the crowdloan to withdraw from. #[pallet::call_index(3)] pub fn withdraw( origin: OriginFor, @@ -350,6 +427,15 @@ pub mod pallet { Ok(()) } + /// Refund a failed crowdloan. + /// + /// The call will try to refund all contributors up to the limit defined by the `RefundContributorsLimit`. + /// If the limit is reached, the call will stop and the crowdloan will be marked as partially refunded. + /// + /// The dispatch origin for this call must be _Signed_ and doesn't need to be the creator of the crowdloan. + /// + /// Parameters: + /// - `crowdloan_id`: The id of the crowdloan to refund. #[pallet::call_index(4)] pub fn refund( origin: OriginFor, @@ -401,7 +487,17 @@ pub mod pallet { Ok(()) } - // Finish + /// Finalize a successful crowdloan. + /// + /// The call will transfer the raised amount to the target address and dispatch the call that + /// was provided when the crowdloan was created. The CurrentCrowdloanId will be set to the + /// crowdloan id being finalized so the dispatched call can access it temporarily by accessing + /// the `CurrentCrowdloanId` storage item. + /// + /// The dispatch origin for this call must be _Signed_ and must be the creator of the crowdloan. + /// + /// Parameters: + /// - `crowdloan_id`: The id of the crowdloan to finalize. #[pallet::call_index(5)] pub fn finalize( origin: OriginFor, @@ -458,6 +554,8 @@ impl Pallet { Crowdloans::::get(crowdloan_id).ok_or(Error::::InvalidCrowdloanId) } + // A crowdloan is considered to have failed if it has ended, has not raised the cap and + // has not been finalized. fn ensure_crowdloan_failed(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { // Has ended let now = frame_system::Pallet::::block_number(); @@ -472,6 +570,8 @@ impl Pallet { Ok(()) } + // A crowdloan is considered to have succeeded if it has ended, has raised the cap and + // has not been finalized. fn ensure_crowdloan_succeeded(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { // Has ended let now = frame_system::Pallet::::block_number(); From bd5d68779df9b79f801562616ce83c97282fda6e Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 3 Apr 2025 12:23:25 +0200 Subject: [PATCH 23/70] remove unused comments --- pallets/crowdloan/src/lib.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 5a5aa02fd6..be9a0a6833 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -557,32 +557,20 @@ impl Pallet { // A crowdloan is considered to have failed if it has ended, has not raised the cap and // has not been finalized. fn ensure_crowdloan_failed(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { - // Has ended let now = frame_system::Pallet::::block_number(); ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); - - // Has not raised cap ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); - - // Has not finalized ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); - Ok(()) } // A crowdloan is considered to have succeeded if it has ended, has raised the cap and // has not been finalized. fn ensure_crowdloan_succeeded(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { - // Has ended let now = frame_system::Pallet::::block_number(); ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); - - // Has raised cap ensure!(crowdloan.raised == crowdloan.cap, Error::::CapNotRaised); - - // Has not finalized ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); - Ok(()) } } From 7e3faaa0fd887c0ff422712f464c500a17dcddd1 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 3 Apr 2025 12:33:29 +0200 Subject: [PATCH 24/70] fix crowdloan pallet docs --- pallets/crowdloan/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index be9a0a6833..00b8c3e19a 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -3,7 +3,7 @@ //! # Crowdloan Pallet //! //! A pallet allowing users to create generic crowdloans and contribute to them, -//! the raised funds are then dispatched to a target address and an extrinsic +//! the raised funds are then transferred to a target address and an extrinsic //! is dispatched, making it reusable for any crowdloan type. use codec::{Decode, Encode}; From c33239b430c4990c3ad4544efa3f83198c273bfb Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 3 Apr 2025 12:35:21 +0200 Subject: [PATCH 25/70] revert spaces in files --- pallets/subtensor/src/lib.rs | 1 - pallets/subtensor/src/macros/config.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index eb381b6807..03438aa637 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -268,7 +268,6 @@ pub mod pallet { /// Additional information about the subnet pub additional: Vec, } - /// ============================ /// ==== Staking + Accounts ==== /// ============================ diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 18acc4577a..af448c8771 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -5,7 +5,6 @@ use frame_support::pallet_macros::pallet_section; /// This can later be imported into the pallet using [`import_section`]. #[pallet_section] mod config { - /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config + pallet_drand::Config { From 2069831e03bbc9bfb0d6336a30f7af772d28c982 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 3 Apr 2025 17:31:34 +0200 Subject: [PATCH 26/70] fix rust --- pallets/crowdloan/Cargo.toml | 3 +++ pallets/crowdloan/src/lib.rs | 10 ++++++---- pallets/crowdloan/src/tests.rs | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pallets/crowdloan/Cargo.toml b/pallets/crowdloan/Cargo.toml index 23dedb9690..aaa542871d 100644 --- a/pallets/crowdloan/Cargo.toml +++ b/pallets/crowdloan/Cargo.toml @@ -36,6 +36,9 @@ std = [ "frame-system/std", "scale-info/std", "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "sp-core/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 00b8c3e19a..0b9c0c20ca 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -1,11 +1,13 @@ -#![cfg_attr(not(feature = "std"), no_std)] - //! # Crowdloan Pallet //! //! A pallet allowing users to create generic crowdloans and contribute to them, //! the raised funds are then transferred to a target address and an extrinsic //! is dispatched, making it reusable for any crowdloan type. +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +use alloc::{boxed::Box, vec}; use codec::{Decode, Encode}; use frame_support::pallet_prelude::*; use frame_support::{ @@ -31,8 +33,8 @@ type CurrencyOf = ::Currency; type BalanceOf = as Currency<::AccountId>>::Balance; /// A struct containing the information about a crowdloan. -#[freeze_struct("175f314cf5c0cebc")] -#[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[freeze_struct("64e250b23f674ef5")] +#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo)] pub struct CrowdloanInfo { /// The creator of the crowdloan. pub creator: AccountId, diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 5d36ca5f06..e29dc8dfef 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -77,7 +77,7 @@ mod pallet_test { #[pallet::error] pub enum Error { - SomeError, + ShouldFail, MissingCurrentCrowdloanId, } @@ -101,7 +101,7 @@ mod pallet_test { #[pallet::call_index(2)] pub fn failing_extrinsic(origin: OriginFor) -> DispatchResult { - Err(Error::::SomeError.into()) + Err(Error::::ShouldFail.into()) } } } @@ -1748,7 +1748,7 @@ fn test_finalize_fails_if_call_fails() { // try finalize the crowdloan assert_err!( Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), - pallet_test::Error::::SomeError + pallet_test::Error::::ShouldFail ); }); } From b732b54823469fcefd1e379791f851e7f918f877 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 7 Apr 2025 12:49:14 +0200 Subject: [PATCH 27/70] fix tests + move to mock --- pallets/crowdloan/src/mock.rs | 193 +++++++++++++++++++++++++++++++++ pallets/crowdloan/src/tests.rs | 178 ++---------------------------- 2 files changed, 202 insertions(+), 169 deletions(-) create mode 100644 pallets/crowdloan/src/mock.rs diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs new file mode 100644 index 0000000000..203a973860 --- /dev/null +++ b/pallets/crowdloan/src/mock.rs @@ -0,0 +1,193 @@ +#![cfg(test)] +use frame_support::{ + PalletId, derive_impl, parameter_types, + traits::{OnFinalize, OnInitialize}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_core::U256; +use sp_runtime::{BuildStorage, traits::IdentityLookup}; + +use crate::{BalanceOf, CrowdloanId, pallet as pallet_crowdloan}; + +type Block = frame_system::mocking::MockBlock; +pub(crate) type AccountOf = ::AccountId; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system = 1, + Balances: pallet_balances = 2, + Crowdloan: pallet_crowdloan = 3, + TestPallet: pallet_test = 4, + } +); + +#[allow(unused)] +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Expected to not panic"); + pallet_balances::GenesisConfig:: { + balances: vec![ + (U256::from(1), 10), + (U256::from(2), 10), + (U256::from(3), 10), + (U256::from(4), 10), + (U256::from(5), 3), + ], + } + .assimilate_storage(&mut t) + .expect("Expected to not panic"); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountId = U256; + type AccountData = pallet_balances::AccountData; + type Lookup = IdentityLookup; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); + pub const MinimumDeposit: u64 = 50; + pub const MinimumContribution: u64 = 10; + pub const MinimumBlockDuration: u64 = 20; + pub const MaximumBlockDuration: u64 = 100; + pub const RefundContributorsLimit: u32 = 2; +} + +impl pallet_crowdloan::Config for Test { + type PalletId = CrowdloanPalletId; + type Currency = Balances; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MinimumDeposit = MinimumDeposit; + type MinimumContribution = MinimumContribution; + type MinimumBlockDuration = MinimumBlockDuration; + type MaximumBlockDuration = MaximumBlockDuration; + type RefundContributorsLimit = RefundContributorsLimit; +} + +// A test pallet used to test some behavior of the crowdloan pallet +#[allow(unused)] +#[frame_support::pallet(dev_mode)] +pub(crate) mod pallet_test { + use super::*; + use frame_support::{ + dispatch::DispatchResult, + pallet_prelude::{OptionQuery, StorageValue}, + }; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_crowdloan::Config {} + + #[pallet::error] + pub enum Error { + ShouldFail, + MissingCurrentCrowdloanId, + } + + #[pallet::storage] + pub type PassedCrowdloanId = StorageValue<_, CrowdloanId, OptionQuery>; + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + pub fn noop(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + #[pallet::call_index(1)] + pub fn set_passed_crowdloan_id(origin: OriginFor) -> DispatchResult { + let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() + .ok_or(Error::::MissingCurrentCrowdloanId)?; + PassedCrowdloanId::::put(crowdloan_id); + Ok(()) + } + + #[pallet::call_index(2)] + pub fn failing_extrinsic(origin: OriginFor) -> DispatchResult { + Err(Error::::ShouldFail.into()) + } + } +} + +impl pallet_test::Config for Test {} + +pub(crate) struct TestState { + block_number: BlockNumberFor, + balances: Vec<(AccountOf, BalanceOf)>, +} + +impl Default for TestState { + fn default() -> Self { + Self { + block_number: 1, + balances: vec![], + } + } +} + +impl TestState { + pub(crate) fn with_block_number(mut self, block_number: BlockNumberFor) -> Self { + self.block_number = block_number; + self + } + + pub(crate) fn with_balance(mut self, who: AccountOf, balance: BalanceOf) -> Self { + self.balances.push((who, balance)); + self + } + + pub(crate) fn build_and_execute(self, test: impl FnOnce()) { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self + .balances + .iter() + .map(|(who, balance)| (*who, *balance)) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(self.block_number)); + ext.execute_with(test); + } +} + +pub(crate) fn last_event() -> RuntimeEvent { + System::events().pop().expect("RuntimeEvent expected").event +} + +pub(crate) fn run_to_block(n: u64) { + while System::block_number() < n { + System::on_finalize(System::block_number()); + Balances::on_finalize(System::block_number()); + System::reset_events(); + System::set_block_number(System::block_number() + 1); + Balances::on_initialize(System::block_number()); + System::on_initialize(System::block_number()); + } +} + +pub(crate) fn noop_call() -> Box { + Box::new(RuntimeCall::TestPallet(pallet_test::Call::::noop {})) +} diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index e29dc8dfef..278b7d3546 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1,177 +1,12 @@ #![cfg(test)] #![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] -use frame_support::{ - PalletId, assert_err, assert_ok, derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, -}; +use frame_support::{assert_err, assert_ok}; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::U256; -use sp_runtime::{BuildStorage, DispatchError, traits::IdentityLookup}; - -use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, pallet as pallet_crowdloan}; - -type Block = frame_system::mocking::MockBlock; -type AccountOf = ::AccountId; - -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system = 1, - Balances: pallet_balances = 2, - Crowdloan: pallet_crowdloan = 3, - TestPallet: pallet_test = 4, - } -); - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Test { - type Block = Block; - type AccountId = U256; - type AccountData = pallet_balances::AccountData; - type Lookup = IdentityLookup; -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Test { - type AccountStore = System; -} - -parameter_types! { - pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); - pub const MinimumDeposit: u64 = 50; - pub const MinimumContribution: u64 = 10; - pub const MinimumBlockDuration: u64 = 20; - pub const MaximumBlockDuration: u64 = 100; - pub const RefundContributorsLimit: u32 = 2; -} - -impl pallet_crowdloan::Config for Test { - type PalletId = CrowdloanPalletId; - type Currency = Balances; - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type MinimumDeposit = MinimumDeposit; - type MinimumContribution = MinimumContribution; - type MinimumBlockDuration = MinimumBlockDuration; - type MaximumBlockDuration = MaximumBlockDuration; - type RefundContributorsLimit = RefundContributorsLimit; -} - -// A test pallet used to test some behavior of the crowdloan pallet -#[allow(unused)] -#[frame_support::pallet(dev_mode)] -mod pallet_test { - use super::*; - use frame_support::{ - dispatch::DispatchResult, - pallet_prelude::{OptionQuery, StorageValue}, - }; - use frame_system::pallet_prelude::OriginFor; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::config] - pub trait Config: frame_system::Config + pallet_crowdloan::Config {} - - #[pallet::error] - pub enum Error { - ShouldFail, - MissingCurrentCrowdloanId, - } - - #[pallet::storage] - pub type PassedCrowdloanId = StorageValue<_, CrowdloanId, OptionQuery>; - - #[pallet::call] - impl Pallet { - #[pallet::call_index(0)] - pub fn noop(origin: OriginFor) -> DispatchResult { - Ok(()) - } - - #[pallet::call_index(1)] - pub fn set_passed_crowdloan_id(origin: OriginFor) -> DispatchResult { - let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() - .ok_or(Error::::MissingCurrentCrowdloanId)?; - PassedCrowdloanId::::put(crowdloan_id); - Ok(()) - } - - #[pallet::call_index(2)] - pub fn failing_extrinsic(origin: OriginFor) -> DispatchResult { - Err(Error::::ShouldFail.into()) - } - } -} +use sp_runtime::DispatchError; -impl pallet_test::Config for Test {} - -pub(crate) struct TestState { - block_number: BlockNumberFor, - balances: Vec<(AccountOf, BalanceOf)>, -} - -impl Default for TestState { - fn default() -> Self { - Self { - block_number: 1, - balances: vec![], - } - } -} - -impl TestState { - fn with_block_number(mut self, block_number: BlockNumberFor) -> Self { - self.block_number = block_number; - self - } - - fn with_balance(mut self, who: AccountOf, balance: BalanceOf) -> Self { - self.balances.push((who, balance)); - self - } - - fn build_and_execute(self, test: impl FnOnce()) { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: self - .balances - .iter() - .map(|(who, balance)| (*who, *balance)) - .collect::>(), - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(self.block_number)); - ext.execute_with(test); - } -} - -pub(crate) fn last_event() -> RuntimeEvent { - System::events().pop().expect("RuntimeEvent expected").event -} - -pub(crate) fn run_to_block(n: u64) { - while System::block_number() < n { - System::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::reset_events(); - System::set_block_number(System::block_number() + 1); - Balances::on_initialize(System::block_number()); - System::on_initialize(System::block_number()); - } -} - -fn noop_call() -> Box { - Box::new(RuntimeCall::TestPallet(pallet_test::Call::::noop {})) -} +use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, mock::*, pallet as pallet_crowdloan}; #[test] fn test_create_succeeds() { @@ -215,7 +50,7 @@ fn test_create_succeeds() { )), deposit ); - // ensure the contributions has been updated + // ensure the contributions has been updated assert_eq!( pallet_crowdloan::Contributions::::get(crowdloan_id, creator), Some(deposit) @@ -231,6 +66,11 @@ fn test_create_succeeds() { } .into() ); + // ensure next crowdloan id is incremented + assert_eq!( + pallet_crowdloan::NextCrowdloanId::::get(), + crowdloan_id + 1 + ); }); } From db922ce9999ea15ffd181588c4d7754514efe3ad Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 7 Apr 2025 15:28:42 +0200 Subject: [PATCH 28/70] fix some tests --- pallets/crowdloan/src/tests.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 278b7d3546..6e2e3e5a24 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -50,11 +50,18 @@ fn test_create_succeeds() { )), deposit ); + // ensure the creator has been deducted the deposit + assert_eq!(Balances::free_balance(creator), 100 - deposit); // ensure the contributions has been updated assert_eq!( pallet_crowdloan::Contributions::::get(crowdloan_id, creator), Some(deposit) ); + // ensure the raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == deposit) + ); // ensure the event is emitted assert_eq!( last_event(), @@ -307,6 +314,10 @@ fn test_contribute_succeeds() { pallet_crowdloan::Contributions::::get(crowdloan_id, creator), Some(100) ); + assert_eq!( + Balances::free_balance(creator), + 200 - amount - initial_deposit + ); // second contribution to the crowdloan let contributor1: AccountOf = U256::from(2); @@ -329,6 +340,7 @@ fn test_contribute_succeeds() { pallet_crowdloan::Contributions::::get(crowdloan_id, contributor1), Some(100) ); + assert_eq!(Balances::free_balance(contributor1), 500 - amount); // third contribution to the crowdloan let contributor2: AccountOf = U256::from(3); @@ -351,6 +363,7 @@ fn test_contribute_succeeds() { pallet_crowdloan::Contributions::::get(crowdloan_id, contributor2), Some(50) ); + assert_eq!(Balances::free_balance(contributor2), 200 - amount); // ensure the contributions are present in the crowdloan account let crowdloan_account_id: AccountOf = @@ -373,7 +386,6 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t TestState::default() .with_balance(U256::from(1), 200) .with_balance(U256::from(2), 500) - .with_balance(U256::from(3), 200) .build_and_execute(|| { // create a crowdloan let creator: AccountOf = U256::from(1); @@ -414,6 +426,10 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t pallet_crowdloan::Contributions::::get(crowdloan_id, creator), Some(100) ); + assert_eq!( + Balances::free_balance(creator), + 200 - amount - initial_deposit + ); // second contribution to the crowdloan above the cap let contributor1: AccountOf = U256::from(2); @@ -436,6 +452,7 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t pallet_crowdloan::Contributions::::get(crowdloan_id, contributor1), Some(200) ); + assert_eq!(Balances::free_balance(contributor1), 500 - 200); // ensure the contributions are present in the crowdloan account up to the cap let crowdloan_account_id: AccountOf = From e2c0c8985bd0c29459e47fcc6f2a25e5d9f3f6f6 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 8 Apr 2025 12:51:40 +0200 Subject: [PATCH 29/70] fix some tests + rename event --- pallets/crowdloan/src/benchmarking.rs | 149 ++++++++++++++++++++++++++ pallets/crowdloan/src/lib.rs | 13 ++- pallets/crowdloan/src/tests.rs | 67 ++++++++---- 3 files changed, 206 insertions(+), 23 deletions(-) create mode 100644 pallets/crowdloan/src/benchmarking.rs diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs new file mode 100644 index 0000000000..3fa8ed646d --- /dev/null +++ b/pallets/crowdloan/src/benchmarking.rs @@ -0,0 +1,149 @@ +//! Benchmarks for Crowdloan Pallet +#![cfg(feature = "runtime-benchmarks")] +use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, CurrencyOf, pallet::*}; +use frame_benchmarking::{account, v2::*}; +use frame_support::traits::{Currency, Get}; +use frame_system::RawOrigin; +use sp_runtime::traits::Zero; + +extern crate alloc; + +const SEED: u32 = 0; + +use alloc::{boxed::Box, vec}; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +#[benchmarks] +mod benchmarks { + + use super::*; + + #[benchmark] + fn create() { + let creator: T::AccountId = whitelisted_caller(); + let deposit = T::MinimumDeposit::get(); + let cap = deposit + deposit; + let now = frame_system::Pallet::::block_number(); + let end = now + T::MaximumBlockDuration::get(); + let target_address = account::("target_address", 0, SEED); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + + #[extrinsic_call] + _( + RawOrigin::Signed(creator.clone()), + deposit, + cap, + end, + target_address.clone(), + call.clone(), + ); + + // ensure the crowdloan is stored correctly + let crowdloan_id = 0; + assert_eq!( + Crowdloans::::get(crowdloan_id), + Some(CrowdloanInfo { + creator: creator.clone(), + deposit, + cap, + end, + raised: deposit, + target_address, + call, + finalized: false, + }) + ); + // ensure the creator has been deducted the deposit + assert!(CurrencyOf::::free_balance(&creator).is_zero()); + // ensure the initial deposit is stored correctly as contribution + assert_eq!( + Contributions::::get(crowdloan_id, &creator), + Some(deposit) + ); + // ensure the raised amount is updated correctly + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit)); + // ensure the crowdloan account has the deposit + assert_eq!( + CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + deposit + ); + // ensure the event is emitted + assert_last_event::( + Event::::Created { + crowdloan_id, + creator, + end, + cap, + } + .into(), + ); + // ensure next crowdloan id is incremented + assert_eq!(NextCrowdloanId::::get(), crowdloan_id + 1); + } + + #[benchmark] + fn contribute() { + // create a crowdloan + let creator: T::AccountId = whitelisted_caller(); + let deposit = T::MinimumDeposit::get(); + let cap = deposit + deposit; + let now = frame_system::Pallet::::block_number(); + let end = now + T::MaximumBlockDuration::get(); + let target_address: T::AccountId = account::("target_address", 0, SEED); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = Pallet::::create( + RawOrigin::Signed(creator.clone()).into(), + deposit, + cap, + end, + target_address.clone(), + call.clone(), + ); + + // setup contributor + let contributor: T::AccountId = account::("contributor", 0, SEED); + let amount: BalanceOf = T::MinimumContribution::get(); + let crowdloan_id: CrowdloanId = 0; + let _ = CurrencyOf::::make_free_balance_be(&contributor, amount); + + #[extrinsic_call] + _(RawOrigin::Signed(contributor.clone()), crowdloan_id, amount); + + // ensure the contribution is stored correctly + assert_eq!( + Contributions::::get(crowdloan_id, &contributor), + Some(amount) + ); + // ensure the contributor has been deducted the amount + assert!(CurrencyOf::::free_balance(&contributor).is_zero()); + // ensure the crowdloan raised amount is updated correctly + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit + amount)); + // ensure the event is emitted + assert_last_event::( + Event::::Contributed { + contributor, + crowdloan_id, + amount, + } + .into(), + ); + // ensure the contribution is present in the crowdloan account + assert_eq!( + CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + deposit + amount + ); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); +} diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 0b9c0c20ca..b8fa759bcb 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -7,7 +7,7 @@ extern crate alloc; -use alloc::{boxed::Box, vec}; +use alloc::{boxed::Box, vec, vec::Vec}; use codec::{Decode, Encode}; use frame_support::pallet_prelude::*; use frame_support::{ @@ -27,10 +27,13 @@ use subtensor_macros::freeze_struct; type CrowdloanId = u32; +mod benchmarking; +mod mock; mod tests; -type CurrencyOf = ::Currency; -type BalanceOf = as Currency<::AccountId>>::Balance; +pub(crate) type CurrencyOf = ::Currency; +pub(crate) type BalanceOf = + as Currency<::AccountId>>::Balance; /// A struct containing the information about a crowdloan. #[freeze_struct("64e250b23f674ef5")] @@ -163,7 +166,7 @@ pub mod pallet { /// A refund was partially processed for a failed crowdloan. PartiallyRefunded { crowdloan_id: CrowdloanId }, /// A refund was fully processed for a failed crowdloan. - Refunded { crowdloan_id: CrowdloanId }, + AllRefunded { crowdloan_id: CrowdloanId }, /// A crowdloan was finalized, funds were transferred and the call was dispatched. Finalized { crowdloan_id: CrowdloanId }, } @@ -481,7 +484,7 @@ pub mod pallet { } if all_refunded { - Self::deposit_event(Event::::Refunded { crowdloan_id }); + Self::deposit_event(Event::::AllRefunded { crowdloan_id }); } else { Self::deposit_event(Event::::PartiallyRefunded { crowdloan_id }); } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 6e2e3e5a24..b347bc8661 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -699,6 +699,13 @@ fn test_withdraw_succeeds() { creator, crowdloan_id )); + + // ensure the creator contribution has been removed + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, creator), + None + ); + // ensure the creator has the correct amount assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); @@ -708,6 +715,13 @@ fn test_withdraw_succeeds() { contributor, crowdloan_id )); + + // ensure the creator contribution has been removed + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, contributor), + None + ); + // ensure the contributor has the correct amount assert_eq!( pallet_balances::Pallet::::free_balance(contributor), @@ -729,23 +743,6 @@ fn test_withdraw_succeeds() { }); } -#[test] -fn test_withdraw_fails_if_bad_origin() { - TestState::default().build_and_execute(|| { - let crowdloan_id: CrowdloanId = 0; - - assert_err!( - Crowdloan::withdraw(RuntimeOrigin::none(), U256::from(1), crowdloan_id), - DispatchError::BadOrigin - ); - - assert_err!( - Crowdloan::withdraw(RuntimeOrigin::root(), U256::from(1), crowdloan_id), - DispatchError::BadOrigin - ); - }); -} - #[test] fn test_withdraw_succeeds_for_another_contributor() { TestState::default() @@ -815,6 +812,23 @@ fn test_withdraw_succeeds_for_another_contributor() { }); } +#[test] +fn test_withdraw_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::withdraw(RuntimeOrigin::none(), U256::from(1), crowdloan_id), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::withdraw(RuntimeOrigin::root(), U256::from(1), crowdloan_id), + DispatchError::BadOrigin + ); + }); +} + #[test] fn test_withdraw_fails_if_crowdloan_does_not_exists() { TestState::default().build_and_execute(|| { @@ -1132,7 +1146,7 @@ fn test_refund_succeeds() { // ensure the event is emitted assert_eq!( last_event(), - pallet_crowdloan::Event::::Refunded { crowdloan_id }.into() + pallet_crowdloan::Event::::AllRefunded { crowdloan_id }.into() ); // ensure creator has the correct amount @@ -1155,6 +1169,23 @@ fn test_refund_succeeds() { pallet_balances::Pallet::::free_balance(contributor4), 100 ); + // ensure each contributor has been removed from the crowdloan + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor), + None + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor2), + None + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor3), + None + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor4), + None + ); }) } From f2b260779a9a3038af5bbec53c1383e73cb34c50 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 8 Apr 2025 12:54:14 +0200 Subject: [PATCH 30/70] include crowdloan pallet into runtime --- Cargo.lock | 1 + Cargo.toml | 1 + runtime/Cargo.toml | 6 ++++++ runtime/src/lib.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 1270d6d10d..101052959b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5628,6 +5628,7 @@ dependencies = [ "pallet-base-fee", "pallet-collective", "pallet-commitments", + "pallet-crowdloan", "pallet-drand", "pallet-ethereum", "pallet-evm", diff --git a/Cargo.toml b/Cargo.toml index 15dd760a57..3d09ddd02b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ pallet-admin-utils = { default-features = false, path = "pallets/admin-utils" } pallet-collective = { default-features = false, path = "pallets/collective" } pallet-commitments = { default-features = false, path = "pallets/commitments" } pallet-registry = { default-features = false, path = "pallets/registry" } +pallet-crowdloan = { default-features = false, path = "pallets/crowdloan" } pallet-subtensor = { default-features = false, path = "pallets/subtensor" } subtensor-custom-rpc = { default-features = false, path = "pallets/subtensor/rpc" } subtensor-custom-rpc-runtime-api = { default-features = false, path = "pallets/subtensor/runtime-api" } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 67add266a4..f417a18afc 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -122,6 +122,9 @@ w3f-bls = { workspace = true } sha2 = { workspace = true } ark-serialize = { workspace = true } +# Crowdloan +pallet-crowdloan = { workspace = true } + [dev-dependencies] frame-metadata = { workspace = true } sp-io = { workspace = true } @@ -191,6 +194,7 @@ std = [ "sp-genesis-builder/std", "subtensor-precompiles/std", "subtensor-runtime-common/std", + "pallet-crowdloan/std", # Frontier "fp-evm/std", "fp-rpc/std", @@ -236,6 +240,7 @@ runtime-benchmarks = [ "pallet-preimage/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", + "pallet-crowdloan/runtime-benchmarks", # EVM + Frontier "pallet-ethereum/runtime-benchmarks", @@ -269,6 +274,7 @@ try-runtime = [ "pallet-admin-utils/try-runtime", "pallet-commitments/try-runtime", "pallet-registry/try-runtime", + "pallet-crowdloan/try-runtime", # EVM + Frontier "fp-self-contained/try-runtime", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 25799a75c1..ea7c44e13e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -14,6 +14,7 @@ mod migrations; use codec::{Compact, Decode, Encode}; use frame_support::traits::Imbalance; use frame_support::{ + PalletId, dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_state, get_preset}, pallet_prelude::Get, @@ -1366,6 +1367,28 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall { } } +// Crowdloan +parameter_types! { + pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); + pub const MinimumDeposit: Balance = 10_000_000_000; // 10 TAO + pub const MinimumContribution: Balance = 100_000_000; // 0.1 TAO + pub const MinimumBlockDuration: BlockNumber = 1000; + pub const MaximumBlockDuration: BlockNumber = 5000; + pub const RefundContributorsLimit: u32 = 5; +} + +impl pallet_crowdloan::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type PalletId = CrowdloanPalletId; + type MinimumDeposit = MinimumDeposit; + type MinimumContribution = MinimumContribution; + type MinimumBlockDuration = MinimumBlockDuration; + type MaximumBlockDuration = MaximumBlockDuration; + type RefundContributorsLimit = RefundContributorsLimit; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub struct Runtime @@ -1400,6 +1423,8 @@ construct_runtime!( BaseFee: pallet_base_fee = 25, Drand: pallet_drand = 26, + + Crowdloan: pallet_crowdloan = 27, } ); @@ -1469,6 +1494,7 @@ mod benches { [pallet_admin_utils, AdminUtils] [pallet_subtensor, SubtensorModule] [pallet_drand, Drand] + [pallet_crowdloan, Crowdloan] ); } From cca8443257f38385a7b96dc951bc35f4409989f2 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 8 Apr 2025 12:54:31 +0200 Subject: [PATCH 31/70] wip benchmarking --- pallets/crowdloan/src/benchmarking.rs | 135 +++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 5 deletions(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 3fa8ed646d..acac82d6fa 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -22,12 +22,11 @@ fn assert_last_event(generic_event: ::RuntimeEvent) { #[benchmarks] mod benchmarks { - use super::*; #[benchmark] fn create() { - let creator: T::AccountId = whitelisted_caller(); + let creator: T::AccountId = account::("creator", 0, SEED); let deposit = T::MinimumDeposit::get(); let cap = deposit + deposit; let now = frame_system::Pallet::::block_number(); @@ -93,7 +92,7 @@ mod benchmarks { #[benchmark] fn contribute() { // create a crowdloan - let creator: T::AccountId = whitelisted_caller(); + let creator: T::AccountId = account::("creator", 0, SEED); let deposit = T::MinimumDeposit::get(); let cap = deposit + deposit; let now = frame_system::Pallet::::block_number(); @@ -129,6 +128,11 @@ mod benchmarks { assert!(CurrencyOf::::free_balance(&contributor).is_zero()); // ensure the crowdloan raised amount is updated correctly assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit + amount)); + // ensure the contribution is present in the crowdloan account + assert_eq!( + CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + deposit + amount + ); // ensure the event is emitted assert_last_event::( Event::::Contributed { @@ -138,11 +142,132 @@ mod benchmarks { } .into(), ); - // ensure the contribution is present in the crowdloan account + } + + #[benchmark] + fn withdraw() { + // create a crowdloan + let creator: T::AccountId = account::("creator", 0, SEED); + let deposit = T::MinimumDeposit::get(); + let cap = deposit + deposit; + let now = frame_system::Pallet::::block_number(); + let end = now + T::MaximumBlockDuration::get(); + let target_address: T::AccountId = account::("target_address", 0, SEED); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = Pallet::::create( + RawOrigin::Signed(creator.clone()).into(), + deposit, + cap, + end, + target_address.clone(), + call.clone(), + ); + + // create contribution + let contributor: T::AccountId = account::("contributor", 0, SEED); + let amount: BalanceOf = T::MinimumContribution::get(); + let crowdloan_id: CrowdloanId = 0; + let _ = CurrencyOf::::make_free_balance_be(&contributor, amount); + let _ = Pallet::::contribute( + RawOrigin::Signed(contributor.clone()).into(), + crowdloan_id, + amount, + ); + + // run to the end of the contribution period + frame_system::Pallet::::set_block_number(end); + + #[extrinsic_call] + _( + RawOrigin::Signed(contributor.clone()), + contributor.clone(), + crowdloan_id, + ); + + // ensure the creator contribution has been removed + assert_eq!(Contributions::::get(crowdloan_id, &contributor), None); + // ensure the contributor has his contribution back in his balance + assert_eq!(CurrencyOf::::free_balance(&contributor), amount); + // ensure the crowdloan account has been deducted the contribution assert_eq!( CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), - deposit + amount + deposit ); + // ensure the crowdloan raised amount is updated correctly + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit)); + // ensure the event is emitted + assert_last_event::( + Event::::Withdrew { + contributor, + crowdloan_id, + amount, + } + .into(), + ); + } + + #[benchmark] + fn refund(k: Linear<3, { T::RefundContributorsLimit::get() }>) { + // create a crowdloan + let creator: T::AccountId = account::("creator", 0, SEED); + let deposit = T::MinimumDeposit::get(); + let cap = deposit + deposit; + let now = frame_system::Pallet::::block_number(); + let end = now + T::MaximumBlockDuration::get(); + let target_address: T::AccountId = account::("target_address", 0, SEED); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = Pallet::::create( + RawOrigin::Signed(creator.clone()).into(), + deposit, + cap, + end, + target_address.clone(), + call.clone(), + ); + + let crowdloan_id: CrowdloanId = 0; + let amount: BalanceOf = T::MinimumContribution::get(); + // create the worst case count of contributors k to be refunded minus the creator + // who is already a contributor + let contributors = k - 1; + for i in 0..contributors { + let contributor: T::AccountId = account::("contributor", i, SEED); + let _ = CurrencyOf::::make_free_balance_be(&contributor, amount); + let _ = Pallet::::contribute( + RawOrigin::Signed(contributor.clone()).into(), + crowdloan_id, + amount, + ); + } + + // run to the end of the contribution period + frame_system::Pallet::::set_block_number(end); + + #[extrinsic_call] + _(RawOrigin::Signed(creator.clone()), crowdloan_id); + + // ensure the creator has been refunded and the contributions is removed + assert_eq!(CurrencyOf::::free_balance(&creator), deposit); + assert_eq!(Contributions::::get(crowdloan_id, &creator), None); + // ensure each contributor has been refunded and the contributions is removed + for i in 0..contributors { + let contributor: T::AccountId = account::("contributor", i, SEED); + assert_eq!(CurrencyOf::::free_balance(&contributor), amount); + assert_eq!(Contributions::::get(crowdloan_id, &contributor), None); + } + // ensure the crowdloan account has been deducted the contributions + assert_eq!( + CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + Zero::zero() + ); + // ensure the raised amount is updated correctly + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == Zero::zero())); + // ensure the event is emitted + assert_last_event::(Event::::AllRefunded { crowdloan_id }.into()); } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); From 4d9904ef1dd448ab929111381078ac2f3355609c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 8 Apr 2025 14:10:11 +0200 Subject: [PATCH 32/70] fix tests + benchmarking finalize --- pallets/crowdloan/src/benchmarking.rs | 51 ++++++++++++++++++++++++++- pallets/crowdloan/src/tests.rs | 6 ++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index acac82d6fa..6f2fb666ea 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -226,7 +226,7 @@ mod benchmarks { cap, end, target_address.clone(), - call.clone(), + call, ); let crowdloan_id: CrowdloanId = 0; @@ -270,5 +270,54 @@ mod benchmarks { assert_last_event::(Event::::AllRefunded { crowdloan_id }.into()); } + #[benchmark] + fn finalize() { + // create a crowdloan + let creator: T::AccountId = account::("creator", 0, SEED); + let deposit = T::MinimumDeposit::get(); + let cap = deposit + deposit; + let now = frame_system::Pallet::::block_number(); + let end = now + T::MaximumBlockDuration::get(); + let target_address: T::AccountId = account::("target_address", 0, SEED); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = Pallet::::create( + RawOrigin::Signed(creator.clone()).into(), + deposit, + cap, + end, + target_address.clone(), + call, + ); + + // create contribution fullfilling the cap + let crowdloan_id: CrowdloanId = 0; + let contributor: T::AccountId = account::("contributor", 0, SEED); + let amount: BalanceOf = cap - deposit; + let _ = CurrencyOf::::make_free_balance_be(&contributor, amount); + let _ = Pallet::::contribute( + RawOrigin::Signed(contributor.clone()).into(), + crowdloan_id, + amount, + ); + + // run to the end of the contribution period + frame_system::Pallet::::set_block_number(end); + + #[extrinsic_call] + _(RawOrigin::Signed(creator.clone()), crowdloan_id); + + // ensure the target address has received the raised amount + assert_eq!( + CurrencyOf::::free_balance(&target_address), + deposit + amount + ); + // ensure the crowdloan has been finalized + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.finalized)); + // ensure the event is emitted + assert_last_event::(Event::::Finalized { crowdloan_id }.into()); + } + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index b347bc8661..b398c71dcc 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1362,6 +1362,12 @@ fn test_finalize_succeeds() { 100 ); + // ensure the crowdloan is marked as finalized + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.finalized) + ); + // ensure the event is emitted assert_eq!( last_event(), From 0fc7c306603bc2936b3618090936a556b6c245c0 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 8 Apr 2025 15:40:56 +0200 Subject: [PATCH 33/70] run benchmarking + add weights to extrinsic --- pallets/crowdloan/src/lib.rs | 35 ++++- pallets/crowdloan/src/mock.rs | 23 +++- pallets/crowdloan/src/weights.rs | 218 +++++++++++++++++++++++++++++++ runtime/src/lib.rs | 3 +- 4 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 pallets/crowdloan/src/weights.rs diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index b8fa759bcb..6d876f851e 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -21,6 +21,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; +use weights::WeightInfo; pub use pallet::*; use subtensor_macros::freeze_struct; @@ -30,6 +31,7 @@ type CrowdloanId = u32; mod benchmarking; mod mock; mod tests; +pub mod weights; pub(crate) type CurrencyOf = ::Currency; pub(crate) type BalanceOf = @@ -64,7 +66,7 @@ type CrowdloanInfoOf = CrowdloanInfo< ::RuntimeCall, >; -#[frame_support::pallet(dev_mode)] +#[frame_support::pallet] pub mod pallet { use super::*; use frame_support::sp_runtime::traits::Dispatchable; @@ -90,6 +92,9 @@ pub mod pallet { /// The currency mechanism. type Currency: ReservableCurrency; + /// The weight information for the pallet. + type WeightInfo: WeightInfo; + /// The pallet id that will be used to derive crowdloan account ids. #[pallet::constant] type PalletId: Get; @@ -212,10 +217,11 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Create a crowdloan that will raise funds up to a maximum cap and if successful, - /// will transfer funds to the target address and dispatch a call. + /// will transfer funds to the target address and dispatch a call (using creator origin). /// /// The initial deposit will be transfered to the crowdloan account and will be refunded - /// in case the crowdloan fails to raise the cap. + /// in case the crowdloan fails to raise the cap. Additionally, the creator will pay for + /// the execution of the call /// /// The dispatch origin for this call must be _Signed_. /// @@ -226,6 +232,15 @@ pub mod pallet { /// - `target_address`: The address to transfer the raised funds to. /// - `call`: The call to dispatch when the crowdloan is finalized. #[pallet::call_index(0)] + #[pallet::weight({ + let di = call.get_dispatch_info(); + let inner_call_weight = match di.pays_fee { + Pays::Yes => di.weight, + Pays::No => Weight::zero(), + }; + let base_weight = T::WeightInfo::create(); + (base_weight.saturating_add(inner_call_weight), Pays::Yes) + })] pub fn create( origin: OriginFor, #[pallet::compact] deposit: BalanceOf, @@ -315,6 +330,7 @@ pub mod pallet { /// - `crowdloan_id`: The id of the crowdloan to contribute to. /// - `amount`: The amount to contribute. #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::contribute())] pub fn contribute( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, @@ -394,6 +410,7 @@ pub mod pallet { /// - `contributor`: The contributor to withdraw from. /// - `crowdloan_id`: The id of the crowdloan to withdraw from. #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::withdraw())] pub fn withdraw( origin: OriginFor, contributor: T::AccountId, @@ -442,10 +459,11 @@ pub mod pallet { /// Parameters: /// - `crowdloan_id`: The id of the crowdloan to refund. #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::refund(T::RefundContributorsLimit::get()))] pub fn refund( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; @@ -485,11 +503,13 @@ pub mod pallet { if all_refunded { Self::deposit_event(Event::::AllRefunded { crowdloan_id }); + // The loop didn't run fully, we refund the unused weights. + Ok(Some(T::WeightInfo::refund(refund_count)).into()) } else { Self::deposit_event(Event::::PartiallyRefunded { crowdloan_id }); + // The loop ran fully, we don't refund anything. + Ok(().into()) } - - Ok(()) } /// Finalize a successful crowdloan. @@ -504,6 +524,7 @@ pub mod pallet { /// Parameters: /// - `crowdloan_id`: The id of the crowdloan to finalize. #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::finalize())] pub fn finalize( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, @@ -530,7 +551,7 @@ pub mod pallet { // can access it temporarily CurrentCrowdloanId::::put(crowdloan_id); - // Dispatch the call + // Dispatch the call with creator origin let call = crowdloan.call.clone(); call.dispatch(frame_system::RawOrigin::Signed(creator).into()) .map(|_| ()) diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs index 203a973860..042e8005f0 100644 --- a/pallets/crowdloan/src/mock.rs +++ b/pallets/crowdloan/src/mock.rs @@ -2,12 +2,13 @@ use frame_support::{ PalletId, derive_impl, parameter_types, traits::{OnFinalize, OnInitialize}, + weights::Weight, }; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::U256; use sp_runtime::{BuildStorage, traits::IdentityLookup}; -use crate::{BalanceOf, CrowdloanId, pallet as pallet_crowdloan}; +use crate::{BalanceOf, CrowdloanId, pallet as pallet_crowdloan, weights::WeightInfo}; type Block = frame_system::mocking::MockBlock; pub(crate) type AccountOf = ::AccountId; @@ -65,6 +66,25 @@ parameter_types! { pub const RefundContributorsLimit: u32 = 2; } +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn create() -> Weight { + Weight::zero() + } + fn contribute() -> Weight { + Weight::zero() + } + fn withdraw() -> Weight { + Weight::zero() + } + fn refund(_k: u32) -> Weight { + Weight::zero() + } + fn finalize() -> Weight { + Weight::zero() + } +} + impl pallet_crowdloan::Config for Test { type PalletId = CrowdloanPalletId; type Currency = Balances; @@ -75,6 +95,7 @@ impl pallet_crowdloan::Config for Test { type MinimumBlockDuration = MinimumBlockDuration; type MaximumBlockDuration = MaximumBlockDuration; type RefundContributorsLimit = RefundContributorsLimit; + type WeightInfo = TestWeightInfo; } // A test pallet used to test some behavior of the crowdloan pallet diff --git a/pallets/crowdloan/src/weights.rs b/pallets/crowdloan/src/weights.rs new file mode 100644 index 0000000000..4f5c908be3 --- /dev/null +++ b/pallets/crowdloan/src/weights.rs @@ -0,0 +1,218 @@ + +//! Autogenerated weights for `pallet_crowdloan` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 +//! DATE: 2025-04-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Ubuntu-2404-noble-amd64-base`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: `1024` + +// Executed Command: +// ./target/production/node-subtensor +// benchmark +// pallet +// --chain=local +// --wasm-execution=compiled +// --pallet=pallet-crowdloan +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --output=pallets/crowdloan/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs +// --allow-missing-host-functions + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_crowdloan`. +pub trait WeightInfo { + fn create() -> Weight; + fn contribute() -> Weight; + fn withdraw() -> Weight; + fn refund(k: u32, ) -> Weight; + fn finalize() -> Weight; +} + +/// Weights for `pallet_crowdloan` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::NextCrowdloanId` (r:1 w:1) + /// Proof: `Crowdloan::NextCrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Contributions` (r:0 w:1) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `156` + // Estimated: `6148` + // Minimum execution time: 38_823_000 picoseconds. + Weight::from_parts(39_734_000, 6148) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Contributions` (r:1 w:1) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + fn contribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `415` + // Estimated: `6148` + // Minimum execution time: 41_007_000 picoseconds. + Weight::from_parts(41_928_000, 6148) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Contributions` (r:1 w:1) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + fn withdraw() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `6148` + // Minimum execution time: 38_292_000 picoseconds. + Weight::from_parts(39_484_000, 6148) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Contributions` (r:6 w:5) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:6 w:6) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + /// The range of component `k` is `[3, 5]`. + fn refund(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `401 + k * (45 ±0)` + // Estimated: `3866 + k * (2579 ±0)` + // Minimum execution time: 95_037_000 picoseconds. + Weight::from_parts(22_058_344, 3866) + // Standard Error: 163_018 + .saturating_add(Weight::from_parts(25_530_344, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(k.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) + /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn finalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `324` + // Estimated: `6148` + // Minimum execution time: 39_624_000 picoseconds. + Weight::from_parts(40_275_000, 6148) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::NextCrowdloanId` (r:1 w:1) + /// Proof: `Crowdloan::NextCrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Contributions` (r:0 w:1) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `156` + // Estimated: `6148` + // Minimum execution time: 38_823_000 picoseconds. + Weight::from_parts(39_734_000, 6148) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Contributions` (r:1 w:1) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + fn contribute() -> Weight { + // Proof Size summary in bytes: + // Measured: `415` + // Estimated: `6148` + // Minimum execution time: 41_007_000 picoseconds. + Weight::from_parts(41_928_000, 6148) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Contributions` (r:1 w:1) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + fn withdraw() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `6148` + // Minimum execution time: 38_292_000 picoseconds. + Weight::from_parts(39_484_000, 6148) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Crowdloan::Contributions` (r:6 w:5) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:6 w:6) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + /// The range of component `k` is `[3, 5]`. + fn refund(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `401 + k * (45 ±0)` + // Estimated: `3866 + k * (2579 ±0)` + // Minimum execution time: 95_037_000 picoseconds. + Weight::from_parts(22_058_344, 3866) + // Standard Error: 163_018 + .saturating_add(Weight::from_parts(25_530_344, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(k.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) + /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn finalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `324` + // Estimated: `6148` + // Minimum execution time: 39_624_000 picoseconds. + Weight::from_parts(40_275_000, 6148) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } +} \ No newline at end of file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ea7c44e13e..3a180ad4ba 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1387,6 +1387,7 @@ impl pallet_crowdloan::Config for Runtime { type MinimumBlockDuration = MinimumBlockDuration; type MaximumBlockDuration = MaximumBlockDuration; type RefundContributorsLimit = RefundContributorsLimit; + type WeightInfo = pallet_crowdloan::weights::SubstrateWeight; } // Create the runtime by composing the FRAME pallets that were previously configured. @@ -1423,7 +1424,7 @@ construct_runtime!( BaseFee: pallet_base_fee = 25, Drand: pallet_drand = 26, - + Crowdloan: pallet_crowdloan = 27, } ); From 8a89914f4fb5bb58ad0fed67079424852026a468 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 8 Apr 2025 17:53:48 +0200 Subject: [PATCH 34/70] fix min/max block duration for crowdloan --- runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3a180ad4ba..529144e75e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1372,8 +1372,8 @@ parameter_types! { pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); pub const MinimumDeposit: Balance = 10_000_000_000; // 10 TAO pub const MinimumContribution: Balance = 100_000_000; // 0.1 TAO - pub const MinimumBlockDuration: BlockNumber = 1000; - pub const MaximumBlockDuration: BlockNumber = 5000; + pub const MinimumBlockDuration: BlockNumber = 50400; // 7 days minimum (7 * 24 * 60 * 60 / 12) + pub const MaximumBlockDuration: BlockNumber = 216000; // 30 days maximum (30 * 24 * 60 * 60 / 12) pub const RefundContributorsLimit: u32 = 5; } From 549dfae3057bb9b8c5bb710b056f0b635a6dc296 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 9 Apr 2025 15:32:37 +0200 Subject: [PATCH 35/70] store call of crowdloan in preimage storage --- Cargo.lock | 1 + pallets/crowdloan/Cargo.toml | 1 + pallets/crowdloan/src/lib.rs | 45 +++++++++++++++++++++++++--------- pallets/crowdloan/src/mock.rs | 22 ++++++++++++++--- pallets/crowdloan/src/tests.rs | 5 ++-- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 101052959b..0328fc0388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6112,6 +6112,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", + "pallet-preimage", "parity-scale-codec", "scale-info", "sp-core", diff --git a/pallets/crowdloan/Cargo.toml b/pallets/crowdloan/Cargo.toml index aaa542871d..e3cfa90dc8 100644 --- a/pallets/crowdloan/Cargo.toml +++ b/pallets/crowdloan/Cargo.toml @@ -24,6 +24,7 @@ sp-std.workspace = true [dev-dependencies] pallet-balances = { default-features = true, workspace = true } +pallet-preimage = { default-features = true, workspace = true } sp-core = { default-features = true, workspace = true } sp-io = { default-features = true, workspace = true } diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 6d876f851e..ecc2db2a71 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -9,18 +9,22 @@ extern crate alloc; use alloc::{boxed::Box, vec, vec::Vec}; use codec::{Decode, Encode}; -use frame_support::pallet_prelude::*; use frame_support::{ PalletId, dispatch::GetDispatchInfo, + pallet_prelude::*, sp_runtime::{ RuntimeDebug, - traits::{AccountIdConversion, CheckedAdd, Zero}, + traits::{AccountIdConversion, CheckedAdd, Dispatchable, Zero}, + }, + traits::{ + Bounded, Currency, Get, IsSubType, QueryPreimage, ReservableCurrency, StorePreimage, + tokens::ExistenceRequirement, }, - traits::{Currency, Get, IsSubType, ReservableCurrency, tokens::ExistenceRequirement}, }; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; +use sp_runtime::traits::{CheckedSub, Saturating}; use weights::WeightInfo; pub use pallet::*; @@ -34,13 +38,17 @@ mod tests; pub mod weights; pub(crate) type CurrencyOf = ::Currency; + pub(crate) type BalanceOf = as Currency<::AccountId>>::Balance; +pub type BoundedCallOf = + Bounded<::RuntimeCall, ::Hashing>; + /// A struct containing the information about a crowdloan. -#[freeze_struct("64e250b23f674ef5")] -#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo)] -pub struct CrowdloanInfo { +#[freeze_struct("de8793ad88ba2969")] +#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CrowdloanInfo { /// The creator of the crowdloan. pub creator: AccountId, /// The initial deposit of the crowdloan from the creator. @@ -54,7 +62,7 @@ pub struct CrowdloanInfo { /// The target address to transfer the raised funds to. pub target_address: AccountId, /// The call to dispatch when the crowdloan is finalized. - pub call: Box, + pub call: Call, /// Whether the crowdloan has been finalized. pub finalized: bool, } @@ -63,14 +71,12 @@ type CrowdloanInfoOf = CrowdloanInfo< ::AccountId, BalanceOf, BlockNumberFor, - ::RuntimeCall, + BoundedCallOf, >; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::sp_runtime::traits::Dispatchable; - use sp_runtime::traits::{CheckedSub, Saturating}; #[pallet::pallet] pub struct Pallet(_); @@ -95,6 +101,9 @@ pub mod pallet { /// The weight information for the pallet. type WeightInfo: WeightInfo; + /// The preimage provider which will be used to store the call to dispatch. + type Preimages: QueryPreimage + StorePreimage; + /// The pallet id that will be used to derive crowdloan account ids. #[pallet::constant] type PalletId: Get; @@ -212,6 +221,8 @@ pub mod pallet { CapNotRaised, /// An underflow occurred. Underflow, + /// Call to dispatch was not found in the preimage storage. + CallUnavailable, } #[pallet::call] @@ -290,7 +301,7 @@ pub mod pallet { cap, raised: deposit, target_address, - call, + call: T::Preimages::bound(*call)?, finalized: false, }, ); @@ -551,8 +562,18 @@ pub mod pallet { // can access it temporarily CurrentCrowdloanId::::put(crowdloan_id); + // Retrieve the call from the preimage storage + let call = match T::Preimages::peek(&crowdloan.call) { + Ok((call, _)) => call, + Err(_) => { + // If the call is not found, we drop it from the preimage storage + // because it's not needed anymore + T::Preimages::drop(&crowdloan.call); + return Err(Error::::CallUnavailable)?; + } + }; + // Dispatch the call with creator origin - let call = crowdloan.call.clone(); call.dispatch(frame_system::RawOrigin::Signed(creator).into()) .map(|_| ()) .map_err(|e| e.error)?; diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs index 042e8005f0..af2d2ba26a 100644 --- a/pallets/crowdloan/src/mock.rs +++ b/pallets/crowdloan/src/mock.rs @@ -4,7 +4,7 @@ use frame_support::{ traits::{OnFinalize, OnInitialize}, weights::Weight, }; -use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot}; use sp_core::U256; use sp_runtime::{BuildStorage, traits::IdentityLookup}; @@ -19,7 +19,8 @@ frame_support::construct_runtime!( System: frame_system = 1, Balances: pallet_balances = 2, Crowdloan: pallet_crowdloan = 3, - TestPallet: pallet_test = 4, + Preimage: pallet_preimage = 4, + TestPallet: pallet_test = 5, } ); @@ -85,17 +86,32 @@ impl WeightInfo for TestWeightInfo { } } +parameter_types! { + pub const PreimageMaxSize: u32 = 4096 * 1024; + pub const PreimageBaseDeposit: u64 = 1; + pub const PreimageByteDeposit: u64 = 1; +} + +impl pallet_preimage::Config for Test { + type WeightInfo = pallet_preimage::weights::SubstrateWeight; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureRoot>; + type Consideration = (); +} + impl pallet_crowdloan::Config for Test { type PalletId = CrowdloanPalletId; type Currency = Balances; type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; + type WeightInfo = TestWeightInfo; + type Preimages = Preimage; type MinimumDeposit = MinimumDeposit; type MinimumContribution = MinimumContribution; type MinimumBlockDuration = MinimumBlockDuration; type MaximumBlockDuration = MaximumBlockDuration; type RefundContributorsLimit = RefundContributorsLimit; - type WeightInfo = TestWeightInfo; } // A test pallet used to test some behavior of the crowdloan pallet diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index b398c71dcc..5a08ad7505 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1,7 +1,7 @@ #![cfg(test)] #![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] -use frame_support::{assert_err, assert_ok}; +use frame_support::{assert_err, assert_ok, traits::StorePreimage}; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::U256; use sp_runtime::DispatchError; @@ -30,6 +30,7 @@ fn test_create_succeeds() { let crowdloan_id = 0; // ensure the crowdloan is stored correctly + let call = pallet_preimage::Pallet::::bound(*noop_call()).unwrap(); assert_eq!( pallet_crowdloan::Crowdloans::::get(crowdloan_id), Some(CrowdloanInfo { @@ -39,7 +40,7 @@ fn test_create_succeeds() { end, raised: deposit, target_address, - call: noop_call(), + call, finalized: false, }) ); From 31aaa8120da69b5873d313dfc211cfe5639f4395 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 9 Apr 2025 15:32:56 +0200 Subject: [PATCH 36/70] update pallet instanciation --- runtime/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 529144e75e..340a4ac033 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1378,16 +1378,17 @@ parameter_types! { } impl pallet_crowdloan::Config for Runtime { + type PalletId = CrowdloanPalletId; type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type Currency = Balances; - type PalletId = CrowdloanPalletId; + type WeightInfo = pallet_crowdloan::weights::SubstrateWeight; + type Preimages = Preimage; type MinimumDeposit = MinimumDeposit; type MinimumContribution = MinimumContribution; type MinimumBlockDuration = MinimumBlockDuration; type MaximumBlockDuration = MaximumBlockDuration; type RefundContributorsLimit = RefundContributorsLimit; - type WeightInfo = pallet_crowdloan::weights::SubstrateWeight; } // Create the runtime by composing the FRAME pallets that were previously configured. From f8660d622284254323bfde9c3d18e7bf6391487f Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 9 Apr 2025 15:33:51 +0200 Subject: [PATCH 37/70] update benchmark --- pallets/crowdloan/src/benchmarking.rs | 4 +- pallets/crowdloan/src/weights.rs | 114 +++++++++++++------------- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 6f2fb666ea..70b3c6b324 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -2,7 +2,7 @@ #![cfg(feature = "runtime-benchmarks")] use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, CurrencyOf, pallet::*}; use frame_benchmarking::{account, v2::*}; -use frame_support::traits::{Currency, Get}; +use frame_support::traits::{Currency, Get, StorePreimage}; use frame_system::RawOrigin; use sp_runtime::traits::Zero; @@ -57,7 +57,7 @@ mod benchmarks { end, raised: deposit, target_address, - call, + call: T::Preimages::bound(*call).unwrap(), finalized: false, }) ); diff --git a/pallets/crowdloan/src/weights.rs b/pallets/crowdloan/src/weights.rs index 4f5c908be3..ed3f2fa0ad 100644 --- a/pallets/crowdloan/src/weights.rs +++ b/pallets/crowdloan/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for `pallet_crowdloan` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 -//! DATE: 2025-04-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `Ubuntu-2404-noble-amd64-base`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: `1024` @@ -44,65 +44,65 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::NextCrowdloanId` (r:1 w:1) - /// Proof: `Crowdloan::NextCrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::NextCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:0 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 38_823_000 picoseconds. - Weight::from_parts(39_734_000, 6148) + // Minimum execution time: 39_013_000 picoseconds. + Weight::from_parts(39_684_000, 6148) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `415` + // Measured: `417` // Estimated: `6148` - // Minimum execution time: 41_007_000 picoseconds. - Weight::from_parts(41_928_000, 6148) + // Minimum execution time: 41_938_000 picoseconds. + Weight::from_parts(42_910_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `375` + // Measured: `377` // Estimated: `6148` - // Minimum execution time: 38_292_000 picoseconds. - Weight::from_parts(39_484_000, 6148) + // Minimum execution time: 41_357_000 picoseconds. + Weight::from_parts(41_928_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:6 w:5) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:6 w:6) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// The range of component `k` is `[3, 5]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `401 + k * (45 ±0)` - // Estimated: `3866 + k * (2579 ±0)` - // Minimum execution time: 95_037_000 picoseconds. - Weight::from_parts(22_058_344, 3866) - // Standard Error: 163_018 - .saturating_add(Weight::from_parts(25_530_344, 0).saturating_mul(k.into())) + // Measured: `403 + k * (45 ±0)` + // Estimated: `3693 + k * (2579 ±0)` + // Minimum execution time: 97_902_000 picoseconds. + Weight::from_parts(19_877_612, 3693) + // Standard Error: 84_111 + .saturating_add(Weight::from_parts(26_865_421, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -110,19 +110,19 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) - /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: - // Measured: `324` + // Measured: `326` // Estimated: `6148` - // Minimum execution time: 39_624_000 picoseconds. - Weight::from_parts(40_275_000, 6148) + // Minimum execution time: 39_955_000 picoseconds. + Weight::from_parts(41_348_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -133,65 +133,65 @@ impl WeightInfo for () { /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::NextCrowdloanId` (r:1 w:1) - /// Proof: `Crowdloan::NextCrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::NextCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:0 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 38_823_000 picoseconds. - Weight::from_parts(39_734_000, 6148) + // Minimum execution time: 39_013_000 picoseconds. + Weight::from_parts(39_684_000, 6148) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `415` + // Measured: `417` // Estimated: `6148` - // Minimum execution time: 41_007_000 picoseconds. - Weight::from_parts(41_928_000, 6148) + // Minimum execution time: 41_938_000 picoseconds. + Weight::from_parts(42_910_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `375` + // Measured: `377` // Estimated: `6148` - // Minimum execution time: 38_292_000 picoseconds. - Weight::from_parts(39_484_000, 6148) + // Minimum execution time: 41_357_000 picoseconds. + Weight::from_parts(41_928_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:6 w:5) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:6 w:6) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// The range of component `k` is `[3, 5]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `401 + k * (45 ±0)` - // Estimated: `3866 + k * (2579 ±0)` - // Minimum execution time: 95_037_000 picoseconds. - Weight::from_parts(22_058_344, 3866) - // Standard Error: 163_018 - .saturating_add(Weight::from_parts(25_530_344, 0).saturating_mul(k.into())) + // Measured: `403 + k * (45 ±0)` + // Estimated: `3693 + k * (2579 ±0)` + // Minimum execution time: 97_902_000 picoseconds. + Weight::from_parts(19_877_612, 3693) + // Standard Error: 84_111 + .saturating_add(Weight::from_parts(26_865_421, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -199,19 +199,19 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) - /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: - // Measured: `324` + // Measured: `326` // Estimated: `6148` - // Minimum execution time: 39_624_000 picoseconds. - Weight::from_parts(40_275_000, 6148) + // Minimum execution time: 39_955_000 picoseconds. + Weight::from_parts(41_348_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } From 321f2fdf0a46b5b87d5662d20dfb4b1c59d702ef Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 9 Apr 2025 15:55:06 +0200 Subject: [PATCH 38/70] fix clippy error --- pallets/crowdloan/src/benchmarking.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 70b3c6b324..6a436f4bf1 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -1,5 +1,6 @@ //! Benchmarks for Crowdloan Pallet #![cfg(feature = "runtime-benchmarks")] +#![allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, CurrencyOf, pallet::*}; use frame_benchmarking::{account, v2::*}; use frame_support::traits::{Currency, Get, StorePreimage}; From 70438e1f0272b7957706640bde68752a412868b1 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 9 Apr 2025 16:27:43 +0200 Subject: [PATCH 39/70] fix tests --- pallets/crowdloan/src/mock.rs | 2 +- pallets/crowdloan/src/tests.rs | 129 ++++++++------------------------- 2 files changed, 31 insertions(+), 100 deletions(-) diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs index af2d2ba26a..531190c6a0 100644 --- a/pallets/crowdloan/src/mock.rs +++ b/pallets/crowdloan/src/mock.rs @@ -64,7 +64,7 @@ parameter_types! { pub const MinimumContribution: u64 = 10; pub const MinimumBlockDuration: u64 = 20; pub const MaximumBlockDuration: u64 = 100; - pub const RefundContributorsLimit: u32 = 2; + pub const RefundContributorsLimit: u32 = 5; } pub struct TestWeightInfo; diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 5a08ad7505..49d7ed4966 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1017,11 +1017,13 @@ fn test_refund_succeeds() { .with_balance(U256::from(3), 100) .with_balance(U256::from(4), 100) .with_balance(U256::from(5), 100) + .with_balance(U256::from(6), 100) + .with_balance(U256::from(7), 100) .build_and_execute(|| { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; - let cap: BalanceOf = 300; + let cap: BalanceOf = 400; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( @@ -1036,42 +1038,17 @@ fn test_refund_succeeds() { // run some blocks run_to_block(10); - // first contribution to the crowdloan + // make 6 contributions to reach 350 raised amount (initial deposit + contributions) let crowdloan_id: CrowdloanId = 0; - let contributor: AccountOf = U256::from(2); - let amount: BalanceOf = 50; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor), - crowdloan_id, - amount - )); - - // second contribution to the crowdloan - let contributor2: AccountOf = U256::from(3); - let amount: BalanceOf = 50; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor2), - crowdloan_id, - amount - )); - - // third contribution to the crowdloan - let contributor3: AccountOf = U256::from(4); - let amount: BalanceOf = 50; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor3), - crowdloan_id, - amount - )); - - // fourth contribution to the crowdloan - let contributor4: AccountOf = U256::from(5); let amount: BalanceOf = 50; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor4), - crowdloan_id, - amount, - )); + for i in 2..8 { + let contributor: AccountOf = U256::from(i); + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + } // run some more blocks past the end of the contribution period run_to_block(60); @@ -1087,12 +1064,12 @@ fn test_refund_succeeds() { pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); assert_eq!( pallet_balances::Pallet::::free_balance(crowdloan_account_id), - 150 // 2 contributors have been refunded so far + 350 - 5 * amount // 5 contributors have been refunded so far ); // ensure raised amount is updated correctly assert!( pallet_crowdloan::Crowdloans::::get(crowdloan_id) - .is_some_and(|c| c.raised == 150) + .is_some_and(|c| c.raised == 350 - 5 * amount) ); // ensure the event is emitted assert_eq!( @@ -1109,31 +1086,6 @@ fn test_refund_succeeds() { crowdloan_id )); - // ensure the crowdloan account has the correct amount - assert_eq!( - pallet_balances::Pallet::::free_balance(crowdloan_account_id), - 50 - ); - // ensure raised amount is updated correctly - assert!( - pallet_crowdloan::Crowdloans::::get(crowdloan_id) - .is_some_and(|c| c.raised == 50) - ); - // ensure the event is emitted - assert_eq!( - last_event(), - pallet_crowdloan::Event::::PartiallyRefunded { crowdloan_id }.into() - ); - - // run some more blocks - run_to_block(80); - - // third round of refund - assert_ok!(Crowdloan::refund( - RuntimeOrigin::signed(creator), - crowdloan_id - )); - // ensure the crowdloan account has the correct amount assert_eq!( pallet_balances::Pallet::::free_balance(crowdloan_account_id), @@ -1144,48 +1096,27 @@ fn test_refund_succeeds() { pallet_crowdloan::Crowdloans::::get(crowdloan_id) .is_some_and(|c| c.raised == 0) ); - // ensure the event is emitted - assert_eq!( - last_event(), - pallet_crowdloan::Event::::AllRefunded { crowdloan_id }.into() - ); // ensure creator has the correct amount assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); - // ensure each contributor has been refunded - assert_eq!( - pallet_balances::Pallet::::free_balance(contributor), - 100 - ); - assert_eq!( - pallet_balances::Pallet::::free_balance(contributor2), - 100 - ); - assert_eq!( - pallet_balances::Pallet::::free_balance(contributor3), - 100 - ); - assert_eq!( - pallet_balances::Pallet::::free_balance(contributor4), - 100 - ); - // ensure each contributor has been removed from the crowdloan - assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor), - None - ); - assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor2), - None - ); - assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor3), - None - ); + // ensure each contributor has been refunded and removed from the crowdloan + for i in 2..8 { + let contributor: AccountOf = U256::from(i); + assert_eq!( + pallet_balances::Pallet::::free_balance(contributor), + 100 + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor), + None + ); + } + + // ensure the event is emitted assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor4), - None + last_event(), + pallet_crowdloan::Event::::AllRefunded { crowdloan_id }.into() ); }) } From d45452aca4705670a68aa2ed8520677cfe51ad6a Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 9 Apr 2025 16:34:03 +0200 Subject: [PATCH 40/70] fix rust --- pallets/crowdloan/Cargo.toml | 4 ++++ pallets/crowdloan/src/benchmarking.rs | 6 +++++- pallets/crowdloan/src/mock.rs | 3 ++- pallets/crowdloan/src/tests.rs | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pallets/crowdloan/Cargo.toml b/pallets/crowdloan/Cargo.toml index e3cfa90dc8..1739a85b7c 100644 --- a/pallets/crowdloan/Cargo.toml +++ b/pallets/crowdloan/Cargo.toml @@ -40,6 +40,8 @@ std = [ "sp-std/std", "sp-io/std", "sp-core/std", + "pallet-balances/std", + "pallet-preimage/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", @@ -47,10 +49,12 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-preimage/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime", "pallet-balances/try-runtime", + "pallet-preimage/try-runtime", ] diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 6a436f4bf1..8bc70e3203 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -1,6 +1,10 @@ //! Benchmarks for Crowdloan Pallet #![cfg(feature = "runtime-benchmarks")] -#![allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] +#![allow( + clippy::arithmetic_side_effects, + clippy::indexing_slicing, + clippy::unwrap_used +)] use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, CurrencyOf, pallet::*}; use frame_benchmarking::{account, v2::*}; use frame_support::traits::{Currency, Get, StorePreimage}; diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs index 531190c6a0..934bc8cbc4 100644 --- a/pallets/crowdloan/src/mock.rs +++ b/pallets/crowdloan/src/mock.rs @@ -1,10 +1,11 @@ #![cfg(test)] +#![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] use frame_support::{ PalletId, derive_impl, parameter_types, traits::{OnFinalize, OnInitialize}, weights::Weight, }; -use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot}; +use frame_system::{EnsureRoot, pallet_prelude::BlockNumberFor}; use sp_core::U256; use sp_runtime::{BuildStorage, traits::IdentityLookup}; diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 49d7ed4966..fec9af3fab 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1108,7 +1108,7 @@ fn test_refund_succeeds() { 100 ); assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, &contributor), + pallet_crowdloan::Contributions::::get(crowdloan_id, contributor), None ); } From a0b5363cea1d17828950a233fa992bd726388b95 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 14 Apr 2025 17:43:18 +0200 Subject: [PATCH 41/70] change crowdloan block duration with fast blocks --- runtime/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 340a4ac033..16d26d7cd6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1372,8 +1372,16 @@ parameter_types! { pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); pub const MinimumDeposit: Balance = 10_000_000_000; // 10 TAO pub const MinimumContribution: Balance = 100_000_000; // 0.1 TAO - pub const MinimumBlockDuration: BlockNumber = 50400; // 7 days minimum (7 * 24 * 60 * 60 / 12) - pub const MaximumBlockDuration: BlockNumber = 216000; // 30 days maximum (30 * 24 * 60 * 60 / 12) + pub const MinimumBlockDuration: BlockNumber = if cfg!(feature = "fast-blocks") { + 50 + } else { + 50400 // 7 days minimum (7 * 24 * 60 * 60 / 12) + }; + pub const MaximumBlockDuration: BlockNumber = if cfg!(feature = "fast-blocks") { + 20000 + } else { + 216000 // 30 days maximum (30 * 24 * 60 * 60 / 12) + }; pub const RefundContributorsLimit: u32 = 5; } From fef947e90dc7093cf627264c8a3a5b53e93a4c69 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 14 Apr 2025 17:45:59 +0200 Subject: [PATCH 42/70] refacto to use balanced/mutable instead of currency trait + allow target address to be skipped --- pallets/crowdloan/src/lib.rs | 63 ++++++++++--------- pallets/crowdloan/src/tests.rs | 112 +++++++++++---------------------- 2 files changed, 70 insertions(+), 105 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index ecc2db2a71..5dfd7c930e 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -15,16 +15,16 @@ use frame_support::{ pallet_prelude::*, sp_runtime::{ RuntimeDebug, - traits::{AccountIdConversion, CheckedAdd, Dispatchable, Zero}, + traits::{AccountIdConversion, Dispatchable, Zero}, }, traits::{ - Bounded, Currency, Get, IsSubType, QueryPreimage, ReservableCurrency, StorePreimage, - tokens::ExistenceRequirement, + Bounded, Get, IsSubType, QueryPreimage, StorePreimage, fungible, fungible::*, + tokens::Preservation, }, }; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; -use sp_runtime::traits::{CheckedSub, Saturating}; +use sp_runtime::traits::CheckedSub; use weights::WeightInfo; pub use pallet::*; @@ -40,13 +40,13 @@ pub mod weights; pub(crate) type CurrencyOf = ::Currency; pub(crate) type BalanceOf = - as Currency<::AccountId>>::Balance; + as fungible::Inspect<::AccountId>>::Balance; pub type BoundedCallOf = Bounded<::RuntimeCall, ::Hashing>; /// A struct containing the information about a crowdloan. -#[freeze_struct("de8793ad88ba2969")] +#[freeze_struct("cae6cf2ef1037fb3")] #[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CrowdloanInfo { /// The creator of the crowdloan. @@ -59,8 +59,10 @@ pub struct CrowdloanInfo { pub cap: Balance, /// The amount raised so far. pub raised: Balance, - /// The target address to transfer the raised funds to. - pub target_address: AccountId, + /// The optional target address to transfer the raised funds to, if not + /// provided, it means the funds will be transferred from on chain logic + /// inside the provided call to dispatch. + pub target_address: Option, /// The call to dispatch when the crowdloan is finalized. pub call: Call, /// Whether the crowdloan has been finalized. @@ -96,7 +98,8 @@ pub mod pallet { + IsType<::RuntimeCall>; /// The currency mechanism. - type Currency: ReservableCurrency; + type Currency: fungible::Balanced + + fungible::Mutate; /// The weight information for the pallet. type WeightInfo: WeightInfo; @@ -257,7 +260,7 @@ pub mod pallet { #[pallet::compact] deposit: BalanceOf, #[pallet::compact] cap: BalanceOf, #[pallet::compact] end: BlockNumberFor, - target_address: T::AccountId, + target_address: Option, call: Box<::RuntimeCall>, ) -> DispatchResult { let creator = ensure_signed(origin)?; @@ -285,7 +288,7 @@ pub mod pallet { // Ensure the creator has enough balance to pay the initial deposit ensure!( - CurrencyOf::::free_balance(&creator) >= deposit, + CurrencyOf::::balance(&creator) >= deposit, Error::::InsufficientBalance ); @@ -314,7 +317,7 @@ pub mod pallet { &creator, &Self::crowdloan_account_id(crowdloan_id), deposit, - ExistenceRequirement::AllowDeath, + Preservation::Expendable, )?; Contributions::::insert(crowdloan_id, &creator, deposit); @@ -366,7 +369,7 @@ pub mod pallet { // and it does not exceed the cap let left_to_raise = crowdloan .cap - .checked_sub(&crowdloan.raised) + .checked_sub(crowdloan.raised) .ok_or(Error::::Underflow)?; // If the contribution would raise the amount above the cap, @@ -376,18 +379,18 @@ pub mod pallet { // Ensure contribution does not overflow the actual raised amount crowdloan.raised = crowdloan .raised - .checked_add(&amount) + .checked_add(amount) .ok_or(Error::::Overflow)?; // Ensure contribution does not overflow the contributor's total contributions let contribution = Contributions::::get(crowdloan_id, &contributor) .unwrap_or(Zero::zero()) - .checked_add(&amount) + .checked_add(amount) .ok_or(Error::::Overflow)?; // Ensure contributor has enough balance to pay ensure!( - CurrencyOf::::free_balance(&contributor) >= amount, + CurrencyOf::::balance(&contributor) >= amount, Error::::InsufficientBalance ); @@ -395,7 +398,7 @@ pub mod pallet { &contributor, &Self::crowdloan_account_id(crowdloan_id), amount, - ExistenceRequirement::AllowDeath, + Preservation::Expendable, )?; Contributions::::insert(crowdloan_id, &contributor, contribution); @@ -441,7 +444,7 @@ pub mod pallet { &Self::crowdloan_account_id(crowdloan_id), &contributor, amount, - ExistenceRequirement::AllowDeath, + Preservation::Expendable, )?; // Remove the contribution from the contributions map and update @@ -497,7 +500,7 @@ pub mod pallet { &crowdloan_account, &contributor, amount, - ExistenceRequirement::AllowDeath, + Preservation::Expendable, )?; refunded_contributors.push(contributor); @@ -525,8 +528,8 @@ pub mod pallet { /// Finalize a successful crowdloan. /// - /// The call will transfer the raised amount to the target address and dispatch the call that - /// was provided when the crowdloan was created. The CurrentCrowdloanId will be set to the + /// The call will transfer the raised amount to the target address if it was provided when the crowdloan was created + /// and dispatch the call that was provided using the creator origin. The CurrentCrowdloanId will be set to the /// crowdloan id being finalized so the dispatched call can access it temporarily by accessing /// the `CurrentCrowdloanId` storage item. /// @@ -550,13 +553,15 @@ pub mod pallet { Error::::ExpectedCreatorOrigin ); - // Transfer the raised amount to the target address - CurrencyOf::::transfer( - &Self::crowdloan_account_id(crowdloan_id), - &crowdloan.target_address, - crowdloan.raised, - ExistenceRequirement::AllowDeath, - )?; + // If the target address is provided, transfer the raised amount to it. + if let Some(ref target_address) = crowdloan.target_address { + CurrencyOf::::transfer( + &Self::crowdloan_account_id(crowdloan_id), + target_address, + crowdloan.raised, + Preservation::Expendable, + )?; + } // Set the current crowdloan id so the dispatched call // can access it temporarily @@ -593,7 +598,7 @@ pub mod pallet { } impl Pallet { - fn crowdloan_account_id(id: CrowdloanId) -> T::AccountId { + pub fn crowdloan_account_id(id: CrowdloanId) -> T::AccountId { T::PalletId::get().into_sub_account_truncating(id) } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index fec9af3fab..68f08aa7f2 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -17,14 +17,13 @@ fn test_create_succeeds() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, cap, end, - target_address, + None, noop_call(), )); @@ -39,7 +38,7 @@ fn test_create_succeeds() { cap, end, raised: deposit, - target_address, + target_address: None, call, finalized: false, }) @@ -88,29 +87,14 @@ fn test_create_fails_if_bad_origin() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_err!( - Crowdloan::create( - RuntimeOrigin::none(), - deposit, - cap, - end, - target_address, - noop_call() - ), + Crowdloan::create(RuntimeOrigin::none(), deposit, cap, end, None, noop_call()), DispatchError::BadOrigin ); assert_err!( - Crowdloan::create( - RuntimeOrigin::root(), - deposit, - cap, - end, - target_address, - noop_call() - ), + Crowdloan::create(RuntimeOrigin::root(), deposit, cap, end, None, noop_call()), DispatchError::BadOrigin ); }); @@ -125,7 +109,6 @@ fn test_create_fails_if_deposit_is_too_low() { let deposit: BalanceOf = 20; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_err!( Crowdloan::create( @@ -133,7 +116,7 @@ fn test_create_fails_if_deposit_is_too_low() { deposit, cap, end, - target_address, + None, noop_call() ), pallet_crowdloan::Error::::DepositTooLow @@ -150,7 +133,6 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { let deposit: BalanceOf = 50; let cap: BalanceOf = 40; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_err!( Crowdloan::create( @@ -158,7 +140,7 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { deposit, cap, end, - target_address, + None, noop_call() ), pallet_crowdloan::Error::::CapTooLow @@ -178,7 +160,6 @@ fn test_create_fails_if_end_is_in_the_past() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = current_block_number - 5; - let target_address: AccountOf = U256::from(42); assert_err!( Crowdloan::create( @@ -186,7 +167,7 @@ fn test_create_fails_if_end_is_in_the_past() { deposit, cap, end, - target_address, + None, noop_call() ), pallet_crowdloan::Error::::CannotEndInPast @@ -203,7 +184,6 @@ fn test_create_fails_if_block_duration_is_too_short() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 11; - let target_address: AccountOf = U256::from(42); assert_err!( Crowdloan::create( @@ -211,7 +191,7 @@ fn test_create_fails_if_block_duration_is_too_short() { deposit, cap, end, - target_address, + None, noop_call() ), pallet_crowdloan::Error::::BlockDurationTooShort @@ -228,7 +208,6 @@ fn test_create_fails_if_block_duration_is_too_long() { let deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 1000; - let target_address: AccountOf = U256::from(42); assert_err!( Crowdloan::create( @@ -236,7 +215,7 @@ fn test_create_fails_if_block_duration_is_too_long() { deposit, cap, end, - target_address, + None, noop_call() ), pallet_crowdloan::Error::::BlockDurationTooLong @@ -253,7 +232,6 @@ fn test_create_fails_if_creator_has_insufficient_balance() { let deposit: BalanceOf = 200; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_err!( Crowdloan::create( @@ -261,7 +239,7 @@ fn test_create_fails_if_creator_has_insufficient_balance() { deposit, cap, end, - target_address, + None, noop_call() ), pallet_crowdloan::Error::::InsufficientBalance @@ -281,13 +259,12 @@ fn test_contribute_succeeds() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -393,13 +370,12 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -516,14 +492,12 @@ fn test_contribute_fails_if_crowdloan_has_ended() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); - assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -553,13 +527,12 @@ fn test_contribute_fails_if_cap_has_been_raised() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -597,13 +570,12 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -632,13 +604,12 @@ fn test_contribute_fails_if_contributor_has_insufficient_balance() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -668,13 +639,12 @@ fn test_withdraw_succeeds() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -704,7 +674,7 @@ fn test_withdraw_succeeds() { // ensure the creator contribution has been removed assert_eq!( pallet_crowdloan::Contributions::::get(crowdloan_id, creator), - None + None, ); // ensure the creator has the correct amount @@ -720,7 +690,7 @@ fn test_withdraw_succeeds() { // ensure the creator contribution has been removed assert_eq!( pallet_crowdloan::Contributions::::get(crowdloan_id, contributor), - None + None, ); // ensure the contributor has the correct amount @@ -755,13 +725,12 @@ fn test_withdraw_succeeds_for_another_contributor() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -858,13 +827,12 @@ fn test_withdraw_fails_if_contribution_period_has_not_ended() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -908,13 +876,12 @@ fn test_withdraw_fails_if_cap_was_fully_raised() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -970,13 +937,12 @@ fn test_withdraw_fails_if_contribution_is_not_found() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -1025,13 +991,12 @@ fn test_refund_succeeds() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 400; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -1109,7 +1074,7 @@ fn test_refund_succeeds() { ); assert_eq!( pallet_crowdloan::Contributions::::get(crowdloan_id, contributor), - None + None, ); } @@ -1163,13 +1128,12 @@ fn test_refund_fails_if_crowdloan_has_not_ended() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -1197,13 +1161,13 @@ fn test_refund_fails_if_crowdloan_has_fully_raised() { let initial_deposit: BalanceOf = 50; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, cap, end, - target_address, + None, noop_call() )); @@ -1244,7 +1208,7 @@ fn test_refund_fails_if_crowdloan_has_fully_raised() { } #[test] -fn test_finalize_succeeds() { +fn test_finalize_succeeds_with_target_address() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) @@ -1255,12 +1219,13 @@ fn test_finalize_succeeds() { let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, cap, end, - target_address, + Some(target_address), Box::new(RuntimeCall::TestPallet( pallet_test::Call::::set_passed_crowdloan_id {} )) @@ -1360,13 +1325,12 @@ fn test_finalize_fails_if_crowdloan_has_not_ended() { let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, cap, end, - target_address, + None, noop_call() )); @@ -1405,13 +1369,12 @@ fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, cap, end, - target_address, + None, noop_call() )); @@ -1450,13 +1413,12 @@ fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, cap, end, - target_address, + None, noop_call() )); @@ -1498,13 +1460,12 @@ fn test_finalize_fails_if_not_creator_origin() { let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, cap, end, - target_address, + None, noop_call() )); @@ -1543,13 +1504,12 @@ fn test_finalize_fails_if_call_fails() { let deposit: BalanceOf = 50; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; - let target_address: AccountOf = U256::from(42); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, cap, end, - target_address, + None, Box::new(RuntimeCall::TestPallet( pallet_test::Call::::failing_extrinsic {} )) From bc498d50197d346af5a0ecf0f992dd1365893a95 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 14 Apr 2025 17:49:32 +0200 Subject: [PATCH 43/70] update doc --- pallets/crowdloan/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/crowdloan/README.md b/pallets/crowdloan/README.md index 172f354d23..459d832533 100644 --- a/pallets/crowdloan/README.md +++ b/pallets/crowdloan/README.md @@ -1,11 +1,11 @@ # Crowdloan Pallet A pallet allowing to create and manage generic crowdloans around a transfer of funds and a arbitrary call. -A user of this pallet can create a crowdloan by providing a deposit, a cap, an end block, a target address and a call. +A user of this pallet can create a crowdloan by providing a deposit, a cap, an end block, a optionnal target address and a call. Users will be able to contribute to the crowdloan by providing funds to the crowdloan they chose to contribute to. -Once the crowdloan is finalized, the funds will be transferred to the target address and the call will be dispatched with the current crowdloan id as a temporary storage item. +Once the crowdloan is finalized, the funds will be transferred to the target address if provided or the end user is expected to transfer them manually on chain if the call is a pallet extrinsic. The call will be dispatched with the current crowdloan id as a temporary storage item. In case the crowdloan fails to raise the cap, the initial deposit will be returned to the creator and contributions will be returned to the contributors. From 00da5d93b539fc677a875d571396ebb3213a3695 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 14 Apr 2025 18:02:54 +0200 Subject: [PATCH 44/70] commit Cargo.lock --- pallets/crowdloan/src/benchmarking.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 8bc70e3203..c555b51a85 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -67,7 +67,7 @@ mod benchmarks { }) ); // ensure the creator has been deducted the deposit - assert!(CurrencyOf::::free_balance(&creator).is_zero()); + assert!(CurrencyOf::::balance(&creator).is_zero()); // ensure the initial deposit is stored correctly as contribution assert_eq!( Contributions::::get(crowdloan_id, &creator), @@ -77,7 +77,7 @@ mod benchmarks { assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit)); // ensure the crowdloan account has the deposit assert_eq!( - CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), deposit ); // ensure the event is emitted @@ -130,12 +130,12 @@ mod benchmarks { Some(amount) ); // ensure the contributor has been deducted the amount - assert!(CurrencyOf::::free_balance(&contributor).is_zero()); + assert!(CurrencyOf::::balance(&contributor).is_zero()); // ensure the crowdloan raised amount is updated correctly assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit + amount)); // ensure the contribution is present in the crowdloan account assert_eq!( - CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), deposit + amount ); // ensure the event is emitted @@ -194,10 +194,10 @@ mod benchmarks { // ensure the creator contribution has been removed assert_eq!(Contributions::::get(crowdloan_id, &contributor), None); // ensure the contributor has his contribution back in his balance - assert_eq!(CurrencyOf::::free_balance(&contributor), amount); + assert_eq!(CurrencyOf::::balance(&contributor), amount); // ensure the crowdloan account has been deducted the contribution assert_eq!( - CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), deposit ); // ensure the crowdloan raised amount is updated correctly @@ -256,17 +256,17 @@ mod benchmarks { _(RawOrigin::Signed(creator.clone()), crowdloan_id); // ensure the creator has been refunded and the contributions is removed - assert_eq!(CurrencyOf::::free_balance(&creator), deposit); + assert_eq!(CurrencyOf::::balance(&creator), deposit); assert_eq!(Contributions::::get(crowdloan_id, &creator), None); // ensure each contributor has been refunded and the contributions is removed for i in 0..contributors { let contributor: T::AccountId = account::("contributor", i, SEED); - assert_eq!(CurrencyOf::::free_balance(&contributor), amount); + assert_eq!(CurrencyOf::::balance(&contributor), amount); assert_eq!(Contributions::::get(crowdloan_id, &contributor), None); } // ensure the crowdloan account has been deducted the contributions assert_eq!( - CurrencyOf::::free_balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), Zero::zero() ); // ensure the raised amount is updated correctly @@ -286,7 +286,7 @@ mod benchmarks { let target_address: T::AccountId = account::("target_address", 0, SEED); let call: Box<::RuntimeCall> = Box::new(frame_system::Call::::remark { remark: vec![] }.into()); - let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = CurrencyOf::::balance(&creator, deposit); let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, @@ -314,10 +314,7 @@ mod benchmarks { _(RawOrigin::Signed(creator.clone()), crowdloan_id); // ensure the target address has received the raised amount - assert_eq!( - CurrencyOf::::free_balance(&target_address), - deposit + amount - ); + assert_eq!(CurrencyOf::::balance(&target_address), deposit + amount); // ensure the crowdloan has been finalized assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.finalized)); // ensure the event is emitted From 6bc719aca9c0efaad572d6f9230cdea58334a493 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 15 Apr 2025 14:50:55 +0200 Subject: [PATCH 45/70] updated benchmarks --- pallets/crowdloan/src/benchmarking.rs | 41 ++++++------ pallets/crowdloan/src/weights.rs | 90 +++++++++++++-------------- 2 files changed, 65 insertions(+), 66 deletions(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index c555b51a85..19a952ec45 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -7,9 +7,8 @@ )] use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, CurrencyOf, pallet::*}; use frame_benchmarking::{account, v2::*}; -use frame_support::traits::{Currency, Get, StorePreimage}; +use frame_support::traits::{Get, fungible::*, StorePreimage}; use frame_system::RawOrigin; -use sp_runtime::traits::Zero; extern crate alloc; @@ -39,7 +38,7 @@ mod benchmarks { let target_address = account::("target_address", 0, SEED); let call: Box<::RuntimeCall> = Box::new(frame_system::Call::::remark { remark: vec![] }.into()); - let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = CurrencyOf::::set_balance(&creator, deposit); #[extrinsic_call] _( @@ -47,7 +46,7 @@ mod benchmarks { deposit, cap, end, - target_address.clone(), + Some(target_address.clone()), call.clone(), ); @@ -61,13 +60,13 @@ mod benchmarks { cap, end, raised: deposit, - target_address, + target_address: Some(target_address.clone()), call: T::Preimages::bound(*call).unwrap(), finalized: false, }) ); // ensure the creator has been deducted the deposit - assert!(CurrencyOf::::balance(&creator).is_zero()); + assert!(CurrencyOf::::balance(&creator) == 0); // ensure the initial deposit is stored correctly as contribution assert_eq!( Contributions::::get(crowdloan_id, &creator), @@ -105,13 +104,13 @@ mod benchmarks { let target_address: T::AccountId = account::("target_address", 0, SEED); let call: Box<::RuntimeCall> = Box::new(frame_system::Call::::remark { remark: vec![] }.into()); - let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = CurrencyOf::::set_balance(&creator, deposit); let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, cap, end, - target_address.clone(), + Some(target_address.clone()), call.clone(), ); @@ -119,7 +118,7 @@ mod benchmarks { let contributor: T::AccountId = account::("contributor", 0, SEED); let amount: BalanceOf = T::MinimumContribution::get(); let crowdloan_id: CrowdloanId = 0; - let _ = CurrencyOf::::make_free_balance_be(&contributor, amount); + let _ = CurrencyOf::::set_balance(&contributor, amount); #[extrinsic_call] _(RawOrigin::Signed(contributor.clone()), crowdloan_id, amount); @@ -130,7 +129,7 @@ mod benchmarks { Some(amount) ); // ensure the contributor has been deducted the amount - assert!(CurrencyOf::::balance(&contributor).is_zero()); + assert!(CurrencyOf::::balance(&contributor) == 0); // ensure the crowdloan raised amount is updated correctly assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit + amount)); // ensure the contribution is present in the crowdloan account @@ -160,13 +159,13 @@ mod benchmarks { let target_address: T::AccountId = account::("target_address", 0, SEED); let call: Box<::RuntimeCall> = Box::new(frame_system::Call::::remark { remark: vec![] }.into()); - let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = CurrencyOf::::set_balance(&creator, deposit); let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, cap, end, - target_address.clone(), + Some(target_address.clone()), call.clone(), ); @@ -174,7 +173,7 @@ mod benchmarks { let contributor: T::AccountId = account::("contributor", 0, SEED); let amount: BalanceOf = T::MinimumContribution::get(); let crowdloan_id: CrowdloanId = 0; - let _ = CurrencyOf::::make_free_balance_be(&contributor, amount); + let _ = CurrencyOf::::set_balance(&contributor, amount); let _ = Pallet::::contribute( RawOrigin::Signed(contributor.clone()).into(), crowdloan_id, @@ -224,13 +223,13 @@ mod benchmarks { let target_address: T::AccountId = account::("target_address", 0, SEED); let call: Box<::RuntimeCall> = Box::new(frame_system::Call::::remark { remark: vec![] }.into()); - let _ = CurrencyOf::::make_free_balance_be(&creator, deposit); + let _ = CurrencyOf::::set_balance(&creator, deposit); let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, cap, end, - target_address.clone(), + Some(target_address.clone()), call, ); @@ -241,7 +240,7 @@ mod benchmarks { let contributors = k - 1; for i in 0..contributors { let contributor: T::AccountId = account::("contributor", i, SEED); - let _ = CurrencyOf::::make_free_balance_be(&contributor, amount); + let _ = CurrencyOf::::set_balance(&contributor, amount); let _ = Pallet::::contribute( RawOrigin::Signed(contributor.clone()).into(), crowdloan_id, @@ -267,10 +266,10 @@ mod benchmarks { // ensure the crowdloan account has been deducted the contributions assert_eq!( CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), - Zero::zero() + 0 ); // ensure the raised amount is updated correctly - assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == Zero::zero())); + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == 0)); // ensure the event is emitted assert_last_event::(Event::::AllRefunded { crowdloan_id }.into()); } @@ -286,13 +285,13 @@ mod benchmarks { let target_address: T::AccountId = account::("target_address", 0, SEED); let call: Box<::RuntimeCall> = Box::new(frame_system::Call::::remark { remark: vec![] }.into()); - let _ = CurrencyOf::::balance(&creator, deposit); + let _ = CurrencyOf::::set_balance(&creator, deposit); let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, cap, end, - target_address.clone(), + Some(target_address.clone()), call, ); @@ -300,7 +299,7 @@ mod benchmarks { let crowdloan_id: CrowdloanId = 0; let contributor: T::AccountId = account::("contributor", 0, SEED); let amount: BalanceOf = cap - deposit; - let _ = CurrencyOf::::make_free_balance_be(&contributor, amount); + let _ = CurrencyOf::::set_balance(&contributor, amount); let _ = Pallet::::contribute( RawOrigin::Signed(contributor.clone()).into(), crowdloan_id, diff --git a/pallets/crowdloan/src/weights.rs b/pallets/crowdloan/src/weights.rs index ed3f2fa0ad..1f01482b1e 100644 --- a/pallets/crowdloan/src/weights.rs +++ b/pallets/crowdloan/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for `pallet_crowdloan` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 -//! DATE: 2025-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-04-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `Ubuntu-2404-noble-amd64-base`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: `1024` @@ -48,48 +48,48 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Crowdloan::Contributions` (r:0 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 39_013_000 picoseconds. - Weight::from_parts(39_684_000, 6148) + // Minimum execution time: 37_730_000 picoseconds. + Weight::from_parts(38_682_000, 6148) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `417` + // Measured: `418` // Estimated: `6148` - // Minimum execution time: 41_938_000 picoseconds. - Weight::from_parts(42_910_000, 6148) + // Minimum execution time: 40_446_000 picoseconds. + Weight::from_parts(41_067_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `377` + // Measured: `378` // Estimated: `6148` - // Minimum execution time: 41_357_000 picoseconds. - Weight::from_parts(41_928_000, 6148) + // Minimum execution time: 38_141_000 picoseconds. + Weight::from_parts(38_823_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:6 w:5) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:6 w:6) @@ -97,12 +97,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `k` is `[3, 5]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403 + k * (45 ±0)` - // Estimated: `3693 + k * (2579 ±0)` - // Minimum execution time: 97_902_000 picoseconds. - Weight::from_parts(19_877_612, 3693) - // Standard Error: 84_111 - .saturating_add(Weight::from_parts(26_865_421, 0).saturating_mul(k.into())) + // Measured: `404 + k * (45 ±0)` + // Estimated: `3694 + k * (2579 ±0)` + // Minimum execution time: 91_791_000 picoseconds. + Weight::from_parts(17_718_125, 3694) + // Standard Error: 65_371 + .saturating_add(Weight::from_parts(25_512_309, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -110,7 +110,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -119,10 +119,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: - // Measured: `326` + // Measured: `327` // Estimated: `6148` - // Minimum execution time: 39_955_000 picoseconds. - Weight::from_parts(41_348_000, 6148) + // Minimum execution time: 38_301_000 picoseconds. + Weight::from_parts(39_314_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -137,48 +137,48 @@ impl WeightInfo for () { /// Storage: `Crowdloan::Contributions` (r:0 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 39_013_000 picoseconds. - Weight::from_parts(39_684_000, 6148) + // Minimum execution time: 37_730_000 picoseconds. + Weight::from_parts(38_682_000, 6148) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `417` + // Measured: `418` // Estimated: `6148` - // Minimum execution time: 41_938_000 picoseconds. - Weight::from_parts(42_910_000, 6148) + // Minimum execution time: 40_446_000 picoseconds. + Weight::from_parts(41_067_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `377` + // Measured: `378` // Estimated: `6148` - // Minimum execution time: 41_357_000 picoseconds. - Weight::from_parts(41_928_000, 6148) + // Minimum execution time: 38_141_000 picoseconds. + Weight::from_parts(38_823_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:6 w:5) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:6 w:6) @@ -186,12 +186,12 @@ impl WeightInfo for () { /// The range of component `k` is `[3, 5]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403 + k * (45 ±0)` - // Estimated: `3693 + k * (2579 ±0)` - // Minimum execution time: 97_902_000 picoseconds. - Weight::from_parts(19_877_612, 3693) - // Standard Error: 84_111 - .saturating_add(Weight::from_parts(26_865_421, 0).saturating_mul(k.into())) + // Measured: `404 + k * (45 ±0)` + // Estimated: `3694 + k * (2579 ±0)` + // Minimum execution time: 91_791_000 picoseconds. + Weight::from_parts(17_718_125, 3694) + // Standard Error: 65_371 + .saturating_add(Weight::from_parts(25_512_309, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -199,7 +199,7 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(228), added: 2703, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -208,10 +208,10 @@ impl WeightInfo for () { /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: - // Measured: `326` + // Measured: `327` // Estimated: `6148` - // Minimum execution time: 39_955_000 picoseconds. - Weight::from_parts(41_348_000, 6148) + // Minimum execution time: 38_301_000 picoseconds. + Weight::from_parts(39_314_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } From 060ba7a382acd5ae1bf851dc944830fc2aa34ea8 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 16 Apr 2025 12:35:49 +0200 Subject: [PATCH 46/70] replace identity hash key types with xxhash --- pallets/crowdloan/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 5dfd7c930e..18859efef8 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -135,7 +135,7 @@ pub mod pallet { /// A map of crowdloan ids to their information. #[pallet::storage] pub type Crowdloans = - StorageMap<_, Identity, CrowdloanId, CrowdloanInfoOf, OptionQuery>; + StorageMap<_, Twox64Concat, CrowdloanId, CrowdloanInfoOf, OptionQuery>; /// The next incrementing crowdloan id. #[pallet::storage] @@ -145,7 +145,7 @@ pub mod pallet { #[pallet::storage] pub type Contributions = StorageDoubleMap< _, - Identity, + Twox64Concat, CrowdloanId, Identity, T::AccountId, @@ -528,7 +528,7 @@ pub mod pallet { /// Finalize a successful crowdloan. /// - /// The call will transfer the raised amount to the target address if it was provided when the crowdloan was created + /// The call will transfer the raised amount to the target address if it was provided when the crowdloan was created /// and dispatch the call that was provided using the creator origin. The CurrentCrowdloanId will be set to the /// crowdloan id being finalized so the dispatched call can access it temporarily by accessing /// the `CurrentCrowdloanId` storage item. From 4b0bce232f299bc15e69ff448f289b436d7cc82c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 16 Apr 2025 14:59:17 +0200 Subject: [PATCH 47/70] added crowdloan funds account to storage for easy access/verification --- pallets/crowdloan/src/benchmarking.rs | 15 ++++---- pallets/crowdloan/src/lib.rs | 50 +++++++++++++------------ pallets/crowdloan/src/tests.rs | 53 ++++++++------------------- 3 files changed, 48 insertions(+), 70 deletions(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 19a952ec45..030d2fb620 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -7,7 +7,7 @@ )] use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, CurrencyOf, pallet::*}; use frame_benchmarking::{account, v2::*}; -use frame_support::traits::{Get, fungible::*, StorePreimage}; +use frame_support::traits::{Get, StorePreimage, fungible::*}; use frame_system::RawOrigin; extern crate alloc; @@ -52,6 +52,7 @@ mod benchmarks { // ensure the crowdloan is stored correctly let crowdloan_id = 0; + let funds_account = Pallet::::funds_account(crowdloan_id); assert_eq!( Crowdloans::::get(crowdloan_id), Some(CrowdloanInfo { @@ -59,6 +60,7 @@ mod benchmarks { deposit, cap, end, + funds_account: funds_account.clone(), raised: deposit, target_address: Some(target_address.clone()), call: T::Preimages::bound(*call).unwrap(), @@ -75,10 +77,7 @@ mod benchmarks { // ensure the raised amount is updated correctly assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit)); // ensure the crowdloan account has the deposit - assert_eq!( - CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), - deposit - ); + assert_eq!(CurrencyOf::::balance(&funds_account), deposit); // ensure the event is emitted assert_last_event::( Event::::Created { @@ -134,7 +133,7 @@ mod benchmarks { assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == deposit + amount)); // ensure the contribution is present in the crowdloan account assert_eq!( - CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + CurrencyOf::::balance(&Pallet::::funds_account(crowdloan_id)), deposit + amount ); // ensure the event is emitted @@ -196,7 +195,7 @@ mod benchmarks { assert_eq!(CurrencyOf::::balance(&contributor), amount); // ensure the crowdloan account has been deducted the contribution assert_eq!( - CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + CurrencyOf::::balance(&Pallet::::funds_account(crowdloan_id)), deposit ); // ensure the crowdloan raised amount is updated correctly @@ -265,7 +264,7 @@ mod benchmarks { } // ensure the crowdloan account has been deducted the contributions assert_eq!( - CurrencyOf::::balance(&Pallet::::crowdloan_account_id(crowdloan_id)), + CurrencyOf::::balance(&Pallet::::funds_account(crowdloan_id)), 0 ); // ensure the raised amount is updated correctly diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 18859efef8..27403f7217 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -46,7 +46,7 @@ pub type BoundedCallOf = Bounded<::RuntimeCall, ::Hashing>; /// A struct containing the information about a crowdloan. -#[freeze_struct("cae6cf2ef1037fb3")] +#[freeze_struct("af44b8def4be3cb9")] #[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CrowdloanInfo { /// The creator of the crowdloan. @@ -57,6 +57,8 @@ pub struct CrowdloanInfo { pub end: BlockNumber, /// The cap to raise. pub cap: Balance, + /// The account holding the funds for this crowdloan. Derived on chain but put here for ease of use. + pub funds_account: AccountId, /// The amount raised so far. pub raised: Balance, /// The optional target address to transfer the raised funds to, if not @@ -294,28 +296,29 @@ pub mod pallet { let crowdloan_id = NextCrowdloanId::::get(); let next_crowdloan_id = crowdloan_id.checked_add(1).ok_or(Error::::Overflow)?; + NextCrowdloanId::::put(next_crowdloan_id); - Crowdloans::::insert( - crowdloan_id, - CrowdloanInfo { - creator: creator.clone(), - deposit, - end, - cap, - raised: deposit, - target_address, - call: T::Preimages::bound(*call)?, - finalized: false, - }, - ); + // Derive the funds account and keep track of it + let funds_account = Self::funds_account(crowdloan_id); + frame_system::Pallet::::inc_providers(&funds_account); - NextCrowdloanId::::put(next_crowdloan_id); + let crowdloan = CrowdloanInfo { + creator: creator.clone(), + deposit, + end, + cap, + funds_account, + raised: deposit, + target_address, + call: T::Preimages::bound(*call)?, + finalized: false, + }; + Crowdloans::::insert(crowdloan_id, &crowdloan); - // Track the crowdloan account and transfer the deposit to the crowdloan account - frame_system::Pallet::::inc_providers(&Self::crowdloan_account_id(crowdloan_id)); + // Transfer the deposit to the funds account CurrencyOf::::transfer( &creator, - &Self::crowdloan_account_id(crowdloan_id), + &crowdloan.funds_account, deposit, Preservation::Expendable, )?; @@ -396,7 +399,7 @@ pub mod pallet { CurrencyOf::::transfer( &contributor, - &Self::crowdloan_account_id(crowdloan_id), + &crowdloan.funds_account, amount, Preservation::Expendable, )?; @@ -441,7 +444,7 @@ pub mod pallet { ensure!(amount > Zero::zero(), Error::::NoContribution); CurrencyOf::::transfer( - &Self::crowdloan_account_id(crowdloan_id), + &crowdloan.funds_account, &contributor, amount, Preservation::Expendable, @@ -481,7 +484,6 @@ pub mod pallet { ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; - let crowdloan_account = Self::crowdloan_account_id(crowdloan_id); Self::ensure_crowdloan_failed(&crowdloan)?; let mut refunded_contributors: Vec = vec![]; @@ -497,7 +499,7 @@ pub mod pallet { } CurrencyOf::::transfer( - &crowdloan_account, + &crowdloan.funds_account, &contributor, amount, Preservation::Expendable, @@ -556,7 +558,7 @@ pub mod pallet { // If the target address is provided, transfer the raised amount to it. if let Some(ref target_address) = crowdloan.target_address { CurrencyOf::::transfer( - &Self::crowdloan_account_id(crowdloan_id), + &crowdloan.funds_account, target_address, crowdloan.raised, Preservation::Expendable, @@ -598,7 +600,7 @@ pub mod pallet { } impl Pallet { - pub fn crowdloan_account_id(id: CrowdloanId) -> T::AccountId { + fn funds_account(id: CrowdloanId) -> T::AccountId { T::PalletId::get().into_sub_account_truncating(id) } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 68f08aa7f2..39b6059fec 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -28,6 +28,7 @@ fn test_create_succeeds() { )); let crowdloan_id = 0; + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); // ensure the crowdloan is stored correctly let call = pallet_preimage::Pallet::::bound(*noop_call()).unwrap(); assert_eq!( @@ -37,6 +38,7 @@ fn test_create_succeeds() { deposit, cap, end, + funds_account: funds_account.clone(), raised: deposit, target_address: None, call, @@ -44,12 +46,7 @@ fn test_create_succeeds() { }) ); // ensure the crowdloan account has the deposit - assert_eq!( - Balances::free_balance(pallet_crowdloan::Pallet::::crowdloan_account_id( - crowdloan_id - )), - deposit - ); + assert_eq!(Balances::free_balance(funds_account), deposit); // ensure the creator has been deducted the deposit assert_eq!(Balances::free_balance(creator), 100 - deposit); // ensure the contributions has been updated @@ -343,13 +340,9 @@ fn test_contribute_succeeds() { ); assert_eq!(Balances::free_balance(contributor2), 200 - amount); - // ensure the contributions are present in the crowdloan account - let crowdloan_account_id: AccountOf = - pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); - assert_eq!( - pallet_balances::Pallet::::free_balance(crowdloan_account_id), - 250 - ); + // ensure the contributions are present in the funds account + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); + assert_eq!(Balances::free_balance(funds_account), 250); // ensure the crowdloan raised amount is updated correctly assert!( @@ -432,12 +425,8 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t assert_eq!(Balances::free_balance(contributor1), 500 - 200); // ensure the contributions are present in the crowdloan account up to the cap - let crowdloan_account_id: AccountOf = - pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); - assert_eq!( - pallet_balances::Pallet::::free_balance(crowdloan_account_id), - 300 - ); + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); + assert_eq!(Balances::free_balance(funds_account), 300); // ensure the crowdloan raised amount is updated correctly assert!( @@ -700,12 +689,8 @@ fn test_withdraw_succeeds() { ); // ensure the crowdloan account has the correct amount - let crowdloan_account_id: AccountOf = - pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); - assert_eq!( - pallet_balances::Pallet::::free_balance(crowdloan_account_id), - 0 - ); + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); + assert_eq!(Balances::free_balance(funds_account), 0); // ensure the crowdloan raised amount is updated correctly assert!( pallet_crowdloan::Crowdloans::::get(crowdloan_id) @@ -767,12 +752,8 @@ fn test_withdraw_succeeds_for_another_contributor() { ); // ensure the crowdloan account has the correct amount - let crowdloan_account_id: AccountOf = - pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); - assert_eq!( - pallet_balances::Pallet::::free_balance(crowdloan_account_id), - 100 - ); + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); + assert_eq!(Balances::free_balance(funds_account), 100); // ensure the crowdloan raised amount is updated correctly assert!( @@ -1025,12 +1006,8 @@ fn test_refund_succeeds() { )); // ensure the crowdloan account has the correct amount - let crowdloan_account_id: AccountOf = - pallet_crowdloan::Pallet::::crowdloan_account_id(crowdloan_id); - assert_eq!( - pallet_balances::Pallet::::free_balance(crowdloan_account_id), - 350 - 5 * amount // 5 contributors have been refunded so far - ); + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); + assert_eq!(Balances::free_balance(funds_account), 350 - 5 * amount); // ensure raised amount is updated correctly assert!( pallet_crowdloan::Crowdloans::::get(crowdloan_id) @@ -1053,7 +1030,7 @@ fn test_refund_succeeds() { // ensure the crowdloan account has the correct amount assert_eq!( - pallet_balances::Pallet::::free_balance(crowdloan_account_id), + pallet_balances::Pallet::::free_balance(funds_account), 0 ); // ensure the raised amount is updated correctly From 550184fac8c2eafa204fa6148c0899cb1c753718 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 16 Apr 2025 14:59:54 +0200 Subject: [PATCH 48/70] update benchmark weights --- pallets/crowdloan/src/weights.rs | 106 +++++++++++++++---------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/pallets/crowdloan/src/weights.rs b/pallets/crowdloan/src/weights.rs index 1f01482b1e..29c81c041d 100644 --- a/pallets/crowdloan/src/weights.rs +++ b/pallets/crowdloan/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for `pallet_crowdloan` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 -//! DATE: 2025-04-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-04-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `Ubuntu-2404-noble-amd64-base`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: `1024` @@ -46,63 +46,63 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Crowdloan::NextCrowdloanId` (r:1 w:1) /// Proof: `Crowdloan::NextCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:0 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 37_730_000 picoseconds. - Weight::from_parts(38_682_000, 6148) + // Minimum execution time: 40_165_000 picoseconds. + Weight::from_parts(40_837_000, 6148) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `467` // Estimated: `6148` - // Minimum execution time: 40_446_000 picoseconds. - Weight::from_parts(41_067_000, 6148) + // Minimum execution time: 42_819_000 picoseconds. + Weight::from_parts(43_782_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `378` + // Measured: `427` // Estimated: `6148` - // Minimum execution time: 38_141_000 picoseconds. - Weight::from_parts(38_823_000, 6148) + // Minimum execution time: 40_656_000 picoseconds. + Weight::from_parts(40_966_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:6 w:5) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:6 w:6) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// The range of component `k` is `[3, 5]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `404 + k * (45 ±0)` - // Estimated: `3694 + k * (2579 ±0)` - // Minimum execution time: 91_791_000 picoseconds. - Weight::from_parts(17_718_125, 3694) - // Standard Error: 65_371 - .saturating_add(Weight::from_parts(25_512_309, 0).saturating_mul(k.into())) + // Measured: `452 + k * (45 ±0)` + // Estimated: `3734 + k * (2579 ±0)` + // Minimum execution time: 97_613_000 picoseconds. + Weight::from_parts(17_688_033, 3734) + // Standard Error: 60_926 + .saturating_add(Weight::from_parts(27_400_437, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -110,7 +110,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -119,10 +119,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: - // Measured: `327` + // Measured: `367` // Estimated: `6148` - // Minimum execution time: 38_301_000 picoseconds. - Weight::from_parts(39_314_000, 6148) + // Minimum execution time: 40_005_000 picoseconds. + Weight::from_parts(40_867_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -135,63 +135,63 @@ impl WeightInfo for () { /// Storage: `Crowdloan::NextCrowdloanId` (r:1 w:1) /// Proof: `Crowdloan::NextCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:0 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 37_730_000 picoseconds. - Weight::from_parts(38_682_000, 6148) + // Minimum execution time: 40_165_000 picoseconds. + Weight::from_parts(40_837_000, 6148) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `467` // Estimated: `6148` - // Minimum execution time: 40_446_000 picoseconds. - Weight::from_parts(41_067_000, 6148) + // Minimum execution time: 42_819_000 picoseconds. + Weight::from_parts(43_782_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `378` + // Measured: `427` // Estimated: `6148` - // Minimum execution time: 38_141_000 picoseconds. - Weight::from_parts(38_823_000, 6148) + // Minimum execution time: 40_656_000 picoseconds. + Weight::from_parts(40_966_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:6 w:5) - /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:6 w:6) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// The range of component `k` is `[3, 5]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `404 + k * (45 ±0)` - // Estimated: `3694 + k * (2579 ±0)` - // Minimum execution time: 91_791_000 picoseconds. - Weight::from_parts(17_718_125, 3694) - // Standard Error: 65_371 - .saturating_add(Weight::from_parts(25_512_309, 0).saturating_mul(k.into())) + // Measured: `452 + k * (45 ±0)` + // Estimated: `3734 + k * (2579 ±0)` + // Minimum execution time: 97_613_000 picoseconds. + Weight::from_parts(17_688_033, 3734) + // Standard Error: 60_926 + .saturating_add(Weight::from_parts(27_400_437, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -199,7 +199,7 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -208,10 +208,10 @@ impl WeightInfo for () { /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: - // Measured: `327` + // Measured: `367` // Estimated: `6148` - // Minimum execution time: 38_301_000 picoseconds. - Weight::from_parts(39_314_000, 6148) + // Minimum execution time: 40_005_000 picoseconds. + Weight::from_parts(40_867_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } From c18b21bd86b1620ec9e57ba023014326956d33fc Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 16 Apr 2025 15:01:19 +0200 Subject: [PATCH 49/70] cargo clippy --- pallets/crowdloan/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 39b6059fec..a064747996 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -38,7 +38,7 @@ fn test_create_succeeds() { deposit, cap, end, - funds_account: funds_account.clone(), + funds_account, raised: deposit, target_address: None, call, From e01f988e87f56f3fb55d9edce3c7fa66d1c3d7f6 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 17 Apr 2025 14:18:00 +0200 Subject: [PATCH 50/70] added absolute initial deposit + refacto create and tests --- pallets/crowdloan/src/lib.rs | 62 +++++++++++++-------- pallets/crowdloan/src/mock.rs | 20 +++---- pallets/crowdloan/src/tests.rs | 99 +++++++++++++++++++++++++++------- 3 files changed, 129 insertions(+), 52 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 27403f7217..e6b667660e 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -46,13 +46,15 @@ pub type BoundedCallOf = Bounded<::RuntimeCall, ::Hashing>; /// A struct containing the information about a crowdloan. -#[freeze_struct("af44b8def4be3cb9")] +#[freeze_struct("2fad4924268058e7")] #[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CrowdloanInfo { /// The creator of the crowdloan. pub creator: AccountId, /// The initial deposit of the crowdloan from the creator. pub deposit: Balance, +/// Minimum contribution to the crowdloan. + pub min_contribution: Balance, /// The end block of the crowdloan. pub end: BlockNumber, /// The cap to raise. @@ -117,9 +119,9 @@ pub mod pallet { #[pallet::constant] type MinimumDeposit: Get>; - /// The minimum contribution required to contribute to a crowdloan. + /// The absolute minimum contribution required to contribute to a crowdloan. #[pallet::constant] - type MinimumContribution: Get>; + type AbsoluteMinimumContribution: Get>; /// The minimum block duration for a crowdloan. #[pallet::constant] @@ -196,6 +198,8 @@ pub mod pallet { DepositTooLow, /// The crowdloan cap is too low. CapTooLow, +/// The minimum contribution is too low. + MinimumContributionTooLow, /// The crowdloan cannot end in the past. CannotEndInPast, /// The crowdloan block duration is too short. @@ -214,8 +218,8 @@ pub mod pallet { ContributionPeriodEnded, /// The contribution is too low. ContributionTooLow, - /// The origin is not from the creator of the crowdloan. - ExpectedCreatorOrigin, + /// The origin of this call is invalid. + InvalidOrigin, /// The crowdloan has already been finalized. AlreadyFinalized, /// The crowdloan contribution period has not ended yet. @@ -233,7 +237,8 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Create a crowdloan that will raise funds up to a maximum cap and if successful, - /// will transfer funds to the target address and dispatch a call (using creator origin). + /// will transfer funds to the target address if provided and dispatch the call + /// (using creator origin). /// /// The initial deposit will be transfered to the crowdloan account and will be refunded /// in case the crowdloan fails to raise the cap. Additionally, the creator will pay for @@ -243,10 +248,11 @@ pub mod pallet { /// /// Parameters: /// - `deposit`: The initial deposit from the creator. +/// - `min_contribution`: The minimum contribution required to contribute to the crowdloan. /// - `cap`: The maximum amount of funds that can be raised. /// - `end`: The block number at which the crowdloan will end. - /// - `target_address`: The address to transfer the raised funds to. - /// - `call`: The call to dispatch when the crowdloan is finalized. + /// - `call`: The call to dispatch when the crowdloan is finalized. +/// - `target_address`: The address to transfer the raised funds to if provided. #[pallet::call_index(0)] #[pallet::weight({ let di = call.get_dispatch_info(); @@ -260,34 +266,29 @@ pub mod pallet { pub fn create( origin: OriginFor, #[pallet::compact] deposit: BalanceOf, +#[pallet::compact] min_contribution: BalanceOf, #[pallet::compact] cap: BalanceOf, #[pallet::compact] end: BlockNumberFor, - target_address: Option, - call: Box<::RuntimeCall>, + call: Box<::RuntimeCall>, +target_address: Option, ) -> DispatchResult { let creator = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); - // Ensure the deposit is at least the minimum deposit and cap is greater + // Ensure the deposit is at least the minimum deposit, cap is greater than deposit + // and the minimum contribution is greater than the absolute minimum contribution. ensure!( deposit >= T::MinimumDeposit::get(), Error::::DepositTooLow ); ensure!(cap > deposit, Error::::CapTooLow); - - // Ensure the end block is after the current block and the duration is - // between the minimum and maximum block duration - ensure!(now < end, Error::::CannotEndInPast); - let block_duration = end.checked_sub(&now).ok_or(Error::::Underflow)?; - ensure!( - block_duration >= T::MinimumBlockDuration::get(), - Error::::BlockDurationTooShort - ); ensure!( - block_duration <= T::MaximumBlockDuration::get(), - Error::::BlockDurationTooLong + min_contribution >= T::AbsoluteMinimumContribution::get(), + Error::::MinimumContributionTooLow ); + Self::ensure_valid_end(now, end)?; + // Ensure the creator has enough balance to pay the initial deposit ensure!( CurrencyOf::::balance(&creator) >= deposit, @@ -305,6 +306,7 @@ pub mod pallet { let crowdloan = CrowdloanInfo { creator: creator.clone(), deposit, +min_contribution, end, cap, funds_account, @@ -627,4 +629,20 @@ impl Pallet { ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); Ok(()) } + + // Ensure the provided end block is after the current block and the duration is + // between the minimum and maximum block duration + fn ensure_valid_end(now: BlockNumberFor, end: BlockNumberFor) -> Result<(), Error> { + ensure!(now < end, Error::::CannotEndInPast); + let block_duration = end.checked_sub(&now).ok_or(Error::::Underflow)?; + ensure!( + block_duration >= T::MinimumBlockDuration::get(), + Error::::BlockDurationTooShort + ); + ensure!( + block_duration <= T::MaximumBlockDuration::get(), + Error::::BlockDurationTooLong + ); + Ok(()) + } } diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs index 934bc8cbc4..7cd8e2e902 100644 --- a/pallets/crowdloan/src/mock.rs +++ b/pallets/crowdloan/src/mock.rs @@ -59,15 +59,6 @@ impl pallet_balances::Config for Test { type AccountStore = System; } -parameter_types! { - pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); - pub const MinimumDeposit: u64 = 50; - pub const MinimumContribution: u64 = 10; - pub const MinimumBlockDuration: u64 = 20; - pub const MaximumBlockDuration: u64 = 100; - pub const RefundContributorsLimit: u32 = 5; -} - pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { fn create() -> Weight { @@ -101,6 +92,15 @@ impl pallet_preimage::Config for Test { type Consideration = (); } +parameter_types! { + pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); + pub const MinimumDeposit: u64 = 50; + pub const AbsoluteMinimumContribution: u64 = 10; + pub const MinimumBlockDuration: u64 = 20; + pub const MaximumBlockDuration: u64 = 100; + pub const RefundContributorsLimit: u32 = 5; +} + impl pallet_crowdloan::Config for Test { type PalletId = CrowdloanPalletId; type Currency = Balances; @@ -109,7 +109,7 @@ impl pallet_crowdloan::Config for Test { type WeightInfo = TestWeightInfo; type Preimages = Preimage; type MinimumDeposit = MinimumDeposit; - type MinimumContribution = MinimumContribution; + type AbsoluteMinimumContribution = AbsoluteMinimumContribution; type MinimumBlockDuration = MinimumBlockDuration; type MaximumBlockDuration = MaximumBlockDuration; type RefundContributorsLimit = RefundContributorsLimit; diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index a064747996..a92a7c6023 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -15,16 +15,18 @@ fn test_create_succeeds() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), - deposit, + deposit, + min_contribution, cap, end, - None, - noop_call(), + noop_call(), +None, )); let crowdloan_id = 0; @@ -36,6 +38,7 @@ fn test_create_succeeds() { Some(CrowdloanInfo { creator, deposit, +min_contribution, cap, end, funds_account, @@ -49,10 +52,11 @@ fn test_create_succeeds() { assert_eq!(Balances::free_balance(funds_account), deposit); // ensure the creator has been deducted the deposit assert_eq!(Balances::free_balance(creator), 100 - deposit); - // ensure the contributions has been updated + // ensure the contributions have been updated assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, creator), - Some(deposit) + pallet_crowdloan::Contributions::::iter_prefix(crowdloan_id) + .collect::>(), + vec![(creator, deposit)] ); // ensure the raised amount is updated correctly assert!( @@ -82,16 +86,33 @@ fn test_create_succeeds() { fn test_create_fails_if_bad_origin() { TestState::default().build_and_execute(|| { let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_err!( - Crowdloan::create(RuntimeOrigin::none(), deposit, cap, end, None, noop_call()), + Crowdloan::create( +RuntimeOrigin::none(), +deposit, + min_contribution, +cap, +end, +noop_call(), + None +), DispatchError::BadOrigin ); assert_err!( - Crowdloan::create(RuntimeOrigin::root(), deposit, cap, end, None, noop_call()), + Crowdloan::create( +RuntimeOrigin::root(), +deposit, + min_contribution, +cap, +end, +noop_call(), + None +), DispatchError::BadOrigin ); }); @@ -104,6 +125,7 @@ fn test_create_fails_if_deposit_is_too_low() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 20; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; @@ -111,10 +133,11 @@ fn test_create_fails_if_deposit_is_too_low() { Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None ), pallet_crowdloan::Error::::DepositTooLow ); @@ -128,6 +151,7 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 40; let end: BlockNumberFor = 50; @@ -135,16 +159,43 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None ), pallet_crowdloan::Error::::CapTooLow ); }); } +#[test] +fn test_create_fails_if_min_contribution_is_too_low() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 5; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + + assert_err!( + Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None + ), + pallet_crowdloan::Error::::MinimumContributionTooLow + ); + }); +} + #[test] fn test_create_fails_if_end_is_in_the_past() { let current_block_number: BlockNumberFor = 10; @@ -155,6 +206,7 @@ fn test_create_fails_if_end_is_in_the_past() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = current_block_number - 5; @@ -162,10 +214,11 @@ fn test_create_fails_if_end_is_in_the_past() { Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None ), pallet_crowdloan::Error::::CannotEndInPast ); @@ -179,6 +232,7 @@ fn test_create_fails_if_block_duration_is_too_short() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 11; @@ -186,10 +240,11 @@ fn test_create_fails_if_block_duration_is_too_short() { Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None ), pallet_crowdloan::Error::::BlockDurationTooShort ); @@ -203,6 +258,7 @@ fn test_create_fails_if_block_duration_is_too_long() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 1000; @@ -210,10 +266,11 @@ fn test_create_fails_if_block_duration_is_too_long() { Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None ), pallet_crowdloan::Error::::BlockDurationTooLong ); @@ -227,6 +284,7 @@ fn test_create_fails_if_creator_has_insufficient_balance() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 200; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; @@ -234,10 +292,11 @@ fn test_create_fails_if_creator_has_insufficient_balance() { Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None ), pallet_crowdloan::Error::::InsufficientBalance ); From 735bcd047a62c1f4420f1ed66da447ae4bf773a2 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 17 Apr 2025 14:44:35 +0200 Subject: [PATCH 51/70] make contribute/withdraw more flexible + fix tests --- pallets/crowdloan/src/lib.rs | 14 +-- pallets/crowdloan/src/tests.rs | 183 +++++++++------------------------ 2 files changed, 54 insertions(+), 143 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index e6b667660e..a9255225fb 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -366,7 +366,7 @@ min_contribution, // Ensure contribution is at least the minimum contribution ensure!( - amount >= T::MinimumContribution::get(), + amount >= crowdloan.min_contribution, Error::::ContributionTooLow ); @@ -387,7 +387,7 @@ min_contribution, .checked_add(amount) .ok_or(Error::::Overflow)?; - // Ensure contribution does not overflow the contributor's total contributions + // Compute the new total contribution and ensure it does not overflow. let contribution = Contributions::::get(crowdloan_id, &contributor) .unwrap_or(Zero::zero()) .checked_add(amount) @@ -410,15 +410,15 @@ min_contribution, Crowdloans::::insert(crowdloan_id, &crowdloan); Self::deposit_event(Event::::Contributed { - contributor, - crowdloan_id, + crowdloan_id, +contributor, amount, }); Ok(()) } - /// Withdraw a contribution from a failed crowdloan. + /// Withdraw a contribution from an active (not yet finalized or dissolved) crowdloan. /// /// The origin doesn't needs to be the contributor, it can be any account, /// making it possible for someone to trigger a refund for a contributor. @@ -438,7 +438,7 @@ min_contribution, ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; - Self::ensure_crowdloan_failed(&crowdloan)?; + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); // Ensure contributor has balance left in the crowdloan account let amount = @@ -453,7 +453,7 @@ min_contribution, )?; // Remove the contribution from the contributions map and update - // refunds so far + // crowdloan raised amount to reflect the withdrawal. Contributions::::remove(crowdloan_id, &contributor); crowdloan.raised = crowdloan.raised.saturating_sub(amount); diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index a92a7c6023..39d68e26e2 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -313,15 +313,18 @@ fn test_contribute_succeeds() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks @@ -420,15 +423,18 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks @@ -530,7 +536,7 @@ fn test_contribute_fails_if_crowdloan_does_not_exist() { } #[test] -fn test_contribute_fails_if_crowdloan_has_ended() { +fn test_contribute_fails_if_contribution_period_ended() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) @@ -538,15 +544,18 @@ fn test_contribute_fails_if_crowdloan_has_ended() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run past the end of the crowdloan @@ -573,15 +582,18 @@ fn test_contribute_fails_if_cap_has_been_raised() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks @@ -616,15 +628,18 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks @@ -650,15 +665,18 @@ fn test_contribute_fails_if_contributor_has_insufficient_balance() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks @@ -685,15 +703,18 @@ fn test_withdraw_succeeds() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks @@ -718,13 +739,11 @@ fn test_withdraw_succeeds() { creator, crowdloan_id )); - // ensure the creator contribution has been removed assert_eq!( pallet_crowdloan::Contributions::::get(crowdloan_id, creator), None, ); - // ensure the creator has the correct amount assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); @@ -734,13 +753,11 @@ fn test_withdraw_succeeds() { contributor, crowdloan_id )); - // ensure the creator contribution has been removed assert_eq!( pallet_crowdloan::Contributions::::get(crowdloan_id, contributor), None, ); - // ensure the contributor has the correct amount assert_eq!( pallet_balances::Pallet::::free_balance(contributor), @@ -767,15 +784,18 @@ fn test_withdraw_succeeds_for_another_contributor() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks @@ -800,10 +820,8 @@ fn test_withdraw_succeeds_for_another_contributor() { creator, crowdloan_id )); - // ensure the creator has the correct amount assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); - // ensure the contributor has the correct amount assert_eq!( pallet_balances::Pallet::::free_balance(contributor), @@ -813,7 +831,6 @@ fn test_withdraw_succeeds_for_another_contributor() { // ensure the crowdloan account has the correct amount let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); assert_eq!(Balances::free_balance(funds_account), 100); - // ensure the crowdloan raised amount is updated correctly assert!( pallet_crowdloan::Crowdloans::::get(crowdloan_id) @@ -857,116 +874,7 @@ fn test_withdraw_fails_if_crowdloan_does_not_exists() { } #[test] -fn test_withdraw_fails_if_contribution_period_has_not_ended() { - TestState::default() - .with_balance(U256::from(1), 100) - .with_balance(U256::from(2), 100) - .build_and_execute(|| { - // create a crowdloan - let creator: AccountOf = U256::from(1); - let initial_deposit: BalanceOf = 50; - let cap: BalanceOf = 300; - let end: BlockNumberFor = 50; - assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(creator), - initial_deposit, - cap, - end, - None, - noop_call() - )); - - // run some blocks - run_to_block(10); - - // contribute to the crowdloan - let contributor: AccountOf = U256::from(2); - let crowdloan_id: CrowdloanId = 0; - let amount: BalanceOf = 100; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor), - crowdloan_id, - amount - )); - - // run some more blocks - run_to_block(20); - - // try to withdraw - assert_err!( - Crowdloan::withdraw( - RuntimeOrigin::signed(contributor), - contributor, - crowdloan_id - ), - pallet_crowdloan::Error::::ContributionPeriodNotEnded - ); - }); -} - -#[test] -fn test_withdraw_fails_if_cap_was_fully_raised() { - TestState::default() - .with_balance(U256::from(1), 100) - .with_balance(U256::from(2), 200) - .with_balance(U256::from(3), 200) - .build_and_execute(|| { - // create a crowdloan - let creator: AccountOf = U256::from(1); - let initial_deposit: BalanceOf = 50; - let cap: BalanceOf = 300; - let end: BlockNumberFor = 50; - assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(creator), - initial_deposit, - cap, - end, - None, - noop_call() - )); - - // run some blocks - run_to_block(10); - - // contribute to the crowdloan - let contributor: AccountOf = U256::from(2); - let crowdloan_id: CrowdloanId = 0; - let amount: BalanceOf = 150; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor), - crowdloan_id, - amount - )); - - // run some more blocks - run_to_block(20); - - // another contribution to the crowdloan - let contributor2: AccountOf = U256::from(3); - let amount: BalanceOf = 100; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor2), - crowdloan_id, - amount - )); - - // run some more blocks past the end of the contribution period - run_to_block(60); - - // try to withdraw - assert_err!( - Crowdloan::withdraw( - RuntimeOrigin::signed(contributor), - contributor, - crowdloan_id - ), - pallet_crowdloan::Error::::CapRaised - ); - }); -} - -#[test] -fn test_withdraw_fails_if_contribution_is_not_found() { +fn test_withdraw_fails_if_no_contribution_exists() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 200) @@ -975,15 +883,18 @@ fn test_withdraw_fails_if_contribution_is_not_found() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks From 2712e78d4d7878892f5097e230c5dfadf8e590f4 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 17 Apr 2025 17:28:25 +0200 Subject: [PATCH 52/70] rework finalize + fix/add tests --- pallets/crowdloan/src/lib.rs | 82 +++++++-- pallets/crowdloan/src/mock.rs | 35 +++- pallets/crowdloan/src/tests.rs | 323 ++++++++++----------------------- 3 files changed, 200 insertions(+), 240 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index a9255225fb..7b1ccb3f30 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -428,7 +428,7 @@ contributor, /// Parameters: /// - `contributor`: The contributor to withdraw from. /// - `crowdloan_id`: The id of the crowdloan to withdraw from. - #[pallet::call_index(3)] + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::withdraw())] pub fn withdraw( origin: OriginFor, @@ -468,6 +468,76 @@ contributor, Ok(()) } + /// Finalize a successful crowdloan. + /// + /// The call will transfer the raised amount to the target address if it was provided when the crowdloan was created + /// and dispatch the call that was provided using the creator origin. The CurrentCrowdloanId will be set to the + /// crowdloan id being finalized so the dispatched call can access it temporarily by accessing + /// the `CurrentCrowdloanId` storage item. + /// + /// The dispatch origin for this call must be _Signed_ and must be the creator of the crowdloan. + /// + /// Parameters: + /// - `crowdloan_id`: The id of the crowdloan to finalize. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::finalize())] + pub fn finalize( + origin: OriginFor, + #[pallet::compact] crowdloan_id: CrowdloanId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + + let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + + // Ensure the origin is the creator of the crowdloan and the crowdloan has ended, + // raised the cap and is not finalized. + ensure!(who == crowdloan.creator, Error::::InvalidOrigin); + ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); + ensure!(crowdloan.raised == crowdloan.cap, Error::::CapNotRaised); + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + + // If the target address is provided, transfer the raised amount to it. + if let Some(ref target_address) = crowdloan.target_address { + CurrencyOf::::transfer( + &crowdloan.funds_account, + target_address, + crowdloan.raised, + Preservation::Expendable, + )?; + } + + // Set the current crowdloan id so the dispatched call + // can access it temporarily + CurrentCrowdloanId::::put(crowdloan_id); + + // Retrieve the call from the preimage storage + let call = match T::Preimages::peek(&crowdloan.call) { + Ok((call, _)) => call, + Err(_) => { + // If the call is not found, we drop it from the preimage storage + // because it's not needed anymore + T::Preimages::drop(&crowdloan.call); + return Err(Error::::CallUnavailable)?; + } + }; + + // Dispatch the call with creator origin + call.dispatch(frame_system::RawOrigin::Signed(who).into()) + .map(|_| ()) + .map_err(|e| e.error)?; + + // Clear the current crowdloan id + CurrentCrowdloanId::::kill(); + + crowdloan.finalized = true; + Crowdloans::::insert(crowdloan_id, &crowdloan); + + Self::deposit_event(Event::::Finalized { crowdloan_id }); + + Ok(()) + } + /// Refund a failed crowdloan. /// /// The call will try to refund all contributors up to the limit defined by the `RefundContributorsLimit`. @@ -620,16 +690,6 @@ impl Pallet { Ok(()) } - // A crowdloan is considered to have succeeded if it has ended, has raised the cap and - // has not been finalized. - fn ensure_crowdloan_succeeded(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { - let now = frame_system::Pallet::::block_number(); - ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); - ensure!(crowdloan.raised == crowdloan.cap, Error::::CapNotRaised); - ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); - Ok(()) - } - // Ensure the provided end block is after the current block and the duration is // between the minimum and maximum block duration fn ensure_valid_end(now: BlockNumberFor, end: BlockNumberFor) -> Result<(), Error> { diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs index 7cd8e2e902..43319fe4ff 100644 --- a/pallets/crowdloan/src/mock.rs +++ b/pallets/crowdloan/src/mock.rs @@ -2,7 +2,7 @@ #![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] use frame_support::{ PalletId, derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, + traits::{OnFinalize, OnInitialize, fungible, fungible::*, tokens::Preservation}, weights::Weight, }; use frame_system::{EnsureRoot, pallet_prelude::BlockNumberFor}; @@ -130,12 +130,16 @@ pub(crate) mod pallet_test { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + pallet_crowdloan::Config {} + pub trait Config: frame_system::Config + pallet_crowdloan::Config { + type Currency: fungible::Balanced + + fungible::Mutate; + } #[pallet::error] pub enum Error { ShouldFail, MissingCurrentCrowdloanId, + CrowdloanDoesNotExist, } #[pallet::storage] @@ -149,21 +153,44 @@ pub(crate) mod pallet_test { } #[pallet::call_index(1)] - pub fn set_passed_crowdloan_id(origin: OriginFor) -> DispatchResult { + pub fn transfer_funds(origin: OriginFor, dest: AccountOf) -> DispatchResult { let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() .ok_or(Error::::MissingCurrentCrowdloanId)?; + let crowdloan = pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .ok_or(Error::::CrowdloanDoesNotExist)?; + PassedCrowdloanId::::put(crowdloan_id); + + ::Currency::transfer( + &crowdloan.funds_account, + &dest, + crowdloan.raised, + Preservation::Expendable, + )?; + Ok(()) } #[pallet::call_index(2)] + pub fn set_passed_crowdloan_id(origin: OriginFor) -> DispatchResult { + let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() + .ok_or(Error::::MissingCurrentCrowdloanId)?; + + PassedCrowdloanId::::put(crowdloan_id); + + Ok(()) + } + + #[pallet::call_index(3)] pub fn failing_extrinsic(origin: OriginFor) -> DispatchResult { Err(Error::::ShouldFail.into()) } } } -impl pallet_test::Config for Test {} +impl pallet_test::Config for Test { + type Currency = Balances; +} pub(crate) struct TestState { block_number: BlockNumberFor, diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 39d68e26e2..1ff1759309 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -686,6 +686,7 @@ min_contribution, let crowdloan_id: CrowdloanId = 0; let contributor: AccountOf = U256::from(2); let amount: BalanceOf = 100; + assert_err!( Crowdloan::contribute(RuntimeOrigin::signed(contributor), crowdloan_id, amount), pallet_crowdloan::Error::::InsufficientBalance @@ -724,6 +725,7 @@ min_contribution, let crowdloan_id: CrowdloanId = 0; let contributor: AccountOf = U256::from(2); let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(contributor), crowdloan_id, @@ -805,6 +807,7 @@ min_contribution, let crowdloan_id: CrowdloanId = 0; let contributor: AccountOf = U256::from(2); let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(contributor), crowdloan_id, @@ -904,6 +907,7 @@ min_contribution, let contributor: AccountOf = U256::from(2); let crowdloan_id: CrowdloanId = 0; let amount: BalanceOf = 100; + assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(contributor), crowdloan_id, @@ -927,229 +931,77 @@ min_contribution, } #[test] -fn test_refund_succeeds() { +fn test_finalize_succeeds() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) - .with_balance(U256::from(3), 100) - .with_balance(U256::from(4), 100) - .with_balance(U256::from(5), 100) - .with_balance(U256::from(6), 100) - .with_balance(U256::from(7), 100) - .build_and_execute(|| { + .build_and_execute(|| { // create a crowdloan let creator: AccountOf = U256::from(1); - let initial_deposit: BalanceOf = 50; - let cap: BalanceOf = 400; + let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), - initial_deposit, + deposit, +min_contribution, cap, end, - None, - noop_call() + Box::new(RuntimeCall::TestPallet( + pallet_test::Call::::transfer_funds { + dest: U256::from(42), + } + )), + None )); // run some blocks run_to_block(10); - // make 6 contributions to reach 350 raised amount (initial deposit + contributions) + // some contribution let crowdloan_id: CrowdloanId = 0; +let contributor: AccountOf = U256::from(2); let amount: BalanceOf = 50; - for i in 2..8 { - let contributor: AccountOf = U256::from(i); + assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(contributor), crowdloan_id, amount )); - } - + // run some more blocks past the end of the contribution period run_to_block(60); - // first round of refund - assert_ok!(Crowdloan::refund( + // finalize the crowdloan + assert_ok!(Crowdloan::finalize( RuntimeOrigin::signed(creator), crowdloan_id )); - // ensure the crowdloan account has the correct amount - let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); - assert_eq!(Balances::free_balance(funds_account), 350 - 5 * amount); - // ensure raised amount is updated correctly - assert!( - pallet_crowdloan::Crowdloans::::get(crowdloan_id) - .is_some_and(|c| c.raised == 350 - 5 * amount) - ); - // ensure the event is emitted + // ensure the transfer was a success from the dispatched call assert_eq!( - last_event(), - pallet_crowdloan::Event::::PartiallyRefunded { crowdloan_id }.into() + pallet_balances::Pallet::::free_balance(U256::from(42)), + 100 ); - // run some more blocks - run_to_block(70); - - // second round of refund - assert_ok!(Crowdloan::refund( - RuntimeOrigin::signed(creator), - crowdloan_id - )); - - // ensure the crowdloan account has the correct amount - assert_eq!( - pallet_balances::Pallet::::free_balance(funds_account), - 0 - ); - // ensure the raised amount is updated correctly + // ensure the crowdloan is marked as finalized assert!( pallet_crowdloan::Crowdloans::::get(crowdloan_id) - .is_some_and(|c| c.raised == 0) + .is_some_and(|c| c.finalized) ); - // ensure creator has the correct amount - assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); - - // ensure each contributor has been refunded and removed from the crowdloan - for i in 2..8 { - let contributor: AccountOf = U256::from(i); - assert_eq!( - pallet_balances::Pallet::::free_balance(contributor), - 100 - ); - assert_eq!( - pallet_crowdloan::Contributions::::get(crowdloan_id, contributor), - None, - ); - } - // ensure the event is emitted assert_eq!( last_event(), - pallet_crowdloan::Event::::AllRefunded { crowdloan_id }.into() - ); - }) -} - -#[test] -fn test_refund_fails_if_bad_origin() { - TestState::default().build_and_execute(|| { - let crowdloan_id: CrowdloanId = 0; - - assert_err!( - Crowdloan::refund(RuntimeOrigin::none(), crowdloan_id), - DispatchError::BadOrigin - ); - - assert_err!( - Crowdloan::refund(RuntimeOrigin::root(), crowdloan_id), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn test_refund_fails_if_crowdloan_does_not_exist() { - TestState::default() - .with_balance(U256::from(1), 100) - .build_and_execute(|| { - let creator: AccountOf = U256::from(1); - let crowdloan_id: CrowdloanId = 0; - - assert_err!( - Crowdloan::refund(RuntimeOrigin::signed(creator), crowdloan_id), - pallet_crowdloan::Error::::InvalidCrowdloanId - ); - }); -} - -#[test] -fn test_refund_fails_if_crowdloan_has_not_ended() { - TestState::default() - .with_balance(U256::from(1), 100) - .build_and_execute(|| { - // create a crowdloan - let creator: AccountOf = U256::from(1); - let initial_deposit: BalanceOf = 50; - let cap: BalanceOf = 300; - let end: BlockNumberFor = 50; - assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(creator), - initial_deposit, - cap, - end, - None, - noop_call() - )); - - // run some blocks - run_to_block(10); - - // try to refund - let crowdloan_id: CrowdloanId = 0; - assert_err!( - Crowdloan::refund(RuntimeOrigin::signed(creator), crowdloan_id), - pallet_crowdloan::Error::::ContributionPeriodNotEnded + pallet_crowdloan::Event::::Finalized { crowdloan_id }.into() ); - }); -} - -#[test] -fn test_refund_fails_if_crowdloan_has_fully_raised() { - TestState::default() - .with_balance(U256::from(1), 100) - .with_balance(U256::from(2), 200) - .with_balance(U256::from(3), 200) - .build_and_execute(|| { - // create a crowdloan - let creator: AccountOf = U256::from(1); - let initial_deposit: BalanceOf = 50; - let cap: BalanceOf = 300; - let end: BlockNumberFor = 50; - - assert_ok!(Crowdloan::create( - RuntimeOrigin::signed(creator), - initial_deposit, - cap, - end, - None, - noop_call() - )); - - // run some blocks - run_to_block(10); - - // first contribution to the crowdloan - let crowdloan_id: CrowdloanId = 0; - let contributor: AccountOf = U256::from(2); - let amount: BalanceOf = 150; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor), - crowdloan_id, - amount - )); - // run some more blocks - run_to_block(20); - - // second contribution to the crowdloan - let contributor2: AccountOf = U256::from(3); - let amount: BalanceOf = 100; - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor2), - crowdloan_id, - amount - )); - - // run some more blocks past the end of the contribution period - run_to_block(60); - - // try to refund - assert_err!( - Crowdloan::refund(RuntimeOrigin::signed(creator), crowdloan_id), - pallet_crowdloan::Error::::CapRaised + // ensure the current crowdloan id was accessible from the dispatched call + assert_eq!( + pallet_test::PassedCrowdloanId::::get(), + Some(crowdloan_id) ); }); } @@ -1163,6 +1015,7 @@ fn test_finalize_succeeds_with_target_address() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); @@ -1170,12 +1023,13 @@ fn test_finalize_succeeds_with_target_address() { assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - Some(target_address), - Box::new(RuntimeCall::TestPallet( + Box::new(RuntimeCall::TestPallet( pallet_test::Call::::set_passed_crowdloan_id {} - )) + )), + Some(target_address), )); // run some blocks @@ -1185,6 +1039,7 @@ fn test_finalize_succeeds_with_target_address() { let crowdloan_id: CrowdloanId = 0; let contributor: AccountOf = U256::from(2); let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(contributor), crowdloan_id, @@ -1262,7 +1117,7 @@ fn test_finalize_fails_if_crowdloan_does_not_exist() { } #[test] -fn test_finalize_fails_if_crowdloan_has_not_ended() { +fn test_finalize_fails_if_not_creator_origin() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) @@ -1270,15 +1125,18 @@ fn test_finalize_fails_if_crowdloan_has_not_ended() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - None, - noop_call() + noop_call(), + None )); // run some blocks @@ -1294,19 +1152,19 @@ fn test_finalize_fails_if_crowdloan_has_not_ended() { amount )); - // run some more blocks before end of contribution period - run_to_block(10); + // run some more blocks past the end of the contribution period + run_to_block(60); - // try to finalize + // try finalize the crowdloan assert_err!( - Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), - pallet_crowdloan::Error::::ContributionPeriodNotEnded + Crowdloan::finalize(RuntimeOrigin::signed(contributor), crowdloan_id), + pallet_crowdloan::Error::::InvalidOrigin ); }); } #[test] -fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { +fn test_finalize_fails_if_crowdloan_has_not_ended() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) @@ -1314,16 +1172,19 @@ fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, +noop_call(), None, - noop_call() - )); + )); // run some blocks run_to_block(10); @@ -1331,26 +1192,27 @@ fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { // some contribution let crowdloan_id: CrowdloanId = 0; let contributor: AccountOf = U256::from(2); - let amount: BalanceOf = 49; // below cap + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(contributor), crowdloan_id, amount )); - // run some more blocks past the end of the contribution period - run_to_block(60); + // run some more blocks before end of contribution period + run_to_block(10); - // try finalize the crowdloan + // try to finalize assert_err!( Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), - pallet_crowdloan::Error::::CapNotRaised + pallet_crowdloan::Error::::ContributionPeriodNotEnded ); }); } #[test] -fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { +fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) @@ -1358,21 +1220,28 @@ fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, +noop_call(), None, - noop_call() - )); + )); + + // run some blocks + run_to_block(10); // some contribution let crowdloan_id: CrowdloanId = 0; let contributor: AccountOf = U256::from(2); - let amount: BalanceOf = 50; + let amount: BalanceOf = 49; // below cap + assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(contributor), crowdloan_id, @@ -1382,22 +1251,16 @@ fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { // run some more blocks past the end of the contribution period run_to_block(60); - // finalize the crowdloan - assert_ok!(Crowdloan::finalize( - RuntimeOrigin::signed(creator), - crowdloan_id - )); - - // try finalize the crowdloan a second time + // try finalize the crowdloan assert_err!( Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), - pallet_crowdloan::Error::::AlreadyFinalized + pallet_crowdloan::Error::::CapNotRaised ); }); } #[test] -fn test_finalize_fails_if_not_creator_origin() { +fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) @@ -1405,24 +1268,25 @@ fn test_finalize_fails_if_not_creator_origin() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, +noop_call(), None, - noop_call() - )); - - // run some blocks - run_to_block(10); + )); // some contribution let crowdloan_id: CrowdloanId = 0; let contributor: AccountOf = U256::from(2); let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( RuntimeOrigin::signed(contributor), crowdloan_id, @@ -1432,10 +1296,16 @@ fn test_finalize_fails_if_not_creator_origin() { // run some more blocks past the end of the contribution period run_to_block(60); - // try finalize the crowdloan +// finalize the crowdloan + assert_ok!(Crowdloan::finalize( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + + // try finalize the crowdloan a second time assert_err!( - Crowdloan::finalize(RuntimeOrigin::signed(contributor), crowdloan_id), - pallet_crowdloan::Error::::ExpectedCreatorOrigin + Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_crowdloan::Error::::AlreadyFinalized ); }); } @@ -1449,17 +1319,20 @@ fn test_finalize_fails_if_call_fails() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; +let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, +min_contribution, cap, end, - None, - Box::new(RuntimeCall::TestPallet( + Box::new(RuntimeCall::TestPallet( pallet_test::Call::::failing_extrinsic {} - )) + )), + None, )); // run some blocks From f3bd17f5aca77ce2f6f28975b7dd363242809cef Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 17 Apr 2025 17:53:00 +0200 Subject: [PATCH 53/70] added dissolve extrinsic + tests --- pallets/crowdloan/src/lib.rs | 43 ++++++--- pallets/crowdloan/src/tests.rs | 154 +++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 13 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 7b1ccb3f30..ce87ce4612 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -162,6 +162,11 @@ pub mod pallet { #[pallet::storage] pub type CurrentCrowdloanId = StorageValue<_, CrowdloanId, OptionQuery>; +/// Scheduled for dissolution + #[pallet::storage] + pub type CrowdloansToDissolve = + StorageMap<_, Twox64Concat, CrowdloanId, CrowdloanInfoOf, OptionQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -538,6 +543,28 @@ contributor, Ok(()) } + /// Dissolve a crowdloan and schedule for refund. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::finalize())] + pub fn dissolve( + origin: OriginFor, + #[pallet::compact] crowdloan_id: CrowdloanId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + + // Only the creator can dissolve the crowdloan + ensure!(who == crowdloan.creator, Error::::InvalidOrigin); + + // Mark for dissolution, will be removed in the on_idle block. + CrowdloansToDissolve::::insert(crowdloan_id, crowdloan); + Crowdloans::::remove(crowdloan_id); + + Ok(()) + } + /// Refund a failed crowdloan. /// /// The call will try to refund all contributors up to the limit defined by the `RefundContributorsLimit`. @@ -547,7 +574,7 @@ contributor, /// /// Parameters: /// - `crowdloan_id`: The id of the crowdloan to refund. - #[pallet::call_index(4)] + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::refund(T::RefundContributorsLimit::get()))] pub fn refund( origin: OriginFor, @@ -600,18 +627,8 @@ contributor, } } - /// Finalize a successful crowdloan. - /// - /// The call will transfer the raised amount to the target address if it was provided when the crowdloan was created - /// and dispatch the call that was provided using the creator origin. The CurrentCrowdloanId will be set to the - /// crowdloan id being finalized so the dispatched call can access it temporarily by accessing - /// the `CurrentCrowdloanId` storage item. - /// - /// The dispatch origin for this call must be _Signed_ and must be the creator of the crowdloan. - /// - /// Parameters: - /// - `crowdloan_id`: The id of the crowdloan to finalize. - #[pallet::call_index(5)] + /// Update min contribution + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::finalize())] pub fn finalize( origin: OriginFor, diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 1ff1759309..f0900ba0f5 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1358,3 +1358,157 @@ min_contribution, ); }); } + +#[test] +fn test_dissolve_succeeds_for_a_running_crowdloan() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + // create a crowdloan + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // dissolve the crowdloan + let crowdloan_id: CrowdloanId = 0; + assert_ok!(Crowdloan::dissolve( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + + // ensure the crowdloan is marked for dissolution + assert!(pallet_crowdloan::CrowdloansToDissolve::::get(crowdloan_id).is_some()); + + // ensure the crowdloan is removed from the crowdloans map + assert!(pallet_crowdloan::Crowdloans::::get(crowdloan_id).is_none()); + }); +} + +#[test] +fn test_dissolve_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::dissolve(RuntimeOrigin::none(), crowdloan_id), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::dissolve(RuntimeOrigin::root(), crowdloan_id), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_dissolve_fails_if_crowdloan_does_not_exist() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + assert_err!( + Crowdloan::dissolve(RuntimeOrigin::signed(U256::from(1)), crowdloan_id), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[test] +fn test_dissolve_fails_if_crowdloan_has_been_finalized() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // run some blocks + run_to_block(10); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // finalize the crowdloan + assert_ok!(Crowdloan::finalize( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + + // try dissolve the crowdloan + assert_err!( + Crowdloan::dissolve(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_crowdloan::Error::::AlreadyFinalized + ); + }); +} + +#[test] +fn test_dissolve_fails_if_origin_is_not_creator() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // run some blocks + run_to_block(10); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + + // try dissolve the crowdloan + assert_err!( + Crowdloan::dissolve(RuntimeOrigin::signed(U256::from(2)), crowdloan_id), + pallet_crowdloan::Error::::InvalidOrigin + ); + }); +} From c9fda719d19457082388bc4f0465e04a541784df Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 18 Apr 2025 17:13:24 +0200 Subject: [PATCH 54/70] new update_min_contribution/update_end/update_cap extrinsics + tests --- pallets/crowdloan/src/lib.rs | 92 +++-- pallets/crowdloan/src/tests.rs | 634 +++++++++++++++++++++++++++++++++ 2 files changed, 690 insertions(+), 36 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index ce87ce4612..09ea8ce4ef 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -630,58 +630,78 @@ contributor, /// Update min contribution #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::finalize())] - pub fn finalize( + pub fn update_min_contribution( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, +#[pallet::compact] new_min_contribution: BalanceOf, ) -> DispatchResult { - let creator = ensure_signed(origin)?; + let who = ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; - Self::ensure_crowdloan_succeeded(&crowdloan)?; + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + // Only the creator can update the min contribution. + ensure!(who == crowdloan.creator, Error::::InvalidOrigin); + +// The new min contribution should be greater than absolute minimum contribution. ensure!( - creator == crowdloan.creator, - Error::::ExpectedCreatorOrigin + new_min_contribution > T::AbsoluteMinimumContribution::get(), + Error::::MinimumContributionTooLow ); - // If the target address is provided, transfer the raised amount to it. - if let Some(ref target_address) = crowdloan.target_address { - CurrencyOf::::transfer( - &crowdloan.funds_account, - target_address, - crowdloan.raised, - Preservation::Expendable, - )?; - } + crowdloan.min_contribution = new_min_contribution; + Crowdloans::::insert(crowdloan_id, &crowdloan); - // Set the current crowdloan id so the dispatched call - // can access it temporarily - CurrentCrowdloanId::::put(crowdloan_id); + Ok(()) + } - // Retrieve the call from the preimage storage - let call = match T::Preimages::peek(&crowdloan.call) { - Ok((call, _)) => call, - Err(_) => { - // If the call is not found, we drop it from the preimage storage - // because it's not needed anymore - T::Preimages::drop(&crowdloan.call); - return Err(Error::::CallUnavailable)?; - } - }; + /// Update end + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::finalize())] + pub fn update_end( + origin: OriginFor, + #[pallet::compact] crowdloan_id: CrowdloanId, + #[pallet::compact] new_end: BlockNumberFor, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); - // Dispatch the call with creator origin - call.dispatch(frame_system::RawOrigin::Signed(creator).into()) - .map(|_| ()) - .map_err(|e| e.error)?; + let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); - // Clear the current crowdloan id - CurrentCrowdloanId::::kill(); + // Only the creator can update the min contribution. + ensure!(who == crowdloan.creator, Error::::InvalidOrigin); - // Mark the crowdloan as finalized - crowdloan.finalized = true; + Self::ensure_valid_end(now, new_end)?; + + crowdloan.end = new_end; Crowdloans::::insert(crowdloan_id, &crowdloan); - Self::deposit_event(Event::::Finalized { crowdloan_id }); + Ok(()) + } + + /// Update cap + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::finalize())] + pub fn update_cap( + origin: OriginFor, + #[pallet::compact] crowdloan_id: CrowdloanId, + #[pallet::compact] new_cap: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // The cap can only be updated if the crowdloan has not been finalized. + let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + + // Only the creator can update the cap. + ensure!(who == crowdloan.creator, Error::::InvalidOrigin); + + // The new cap should be greater than the actual raised amount. + ensure!(new_cap > crowdloan.raised, Error::::CapTooLow); + + crowdloan.cap = new_cap; + Crowdloans::::insert(crowdloan_id, &crowdloan); Ok(()) } diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index f0900ba0f5..ed259baee4 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1512,3 +1512,637 @@ fn test_dissolve_fails_if_origin_is_not_creator() { ); }); } + +#[test] +fn test_update_min_contribution_succeeds() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + // create a crowdloan + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + let crowdloan_id: CrowdloanId = 0; + let new_min_contribution: BalanceOf = 20; + + // update the min contribution + assert_ok!(Crowdloan::update_min_contribution( + RuntimeOrigin::signed(creator), + crowdloan_id, + new_min_contribution + )); + + // ensure the min contribution is updated + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.min_contribution == new_min_contribution) + ); + }); +} + +#[test] +fn test_update_min_contribution_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::update_min_contribution(RuntimeOrigin::none(), crowdloan_id, 20), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::update_min_contribution(RuntimeOrigin::root(), crowdloan_id, 20), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_update_min_contribution_fails_if_crowdloan_does_not_exist() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::update_min_contribution( + RuntimeOrigin::signed(U256::from(1)), + crowdloan_id, + 20 + ), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[test] +fn test_update_min_contribution_fails_if_crowdloan_has_been_finalized() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some blocks + run_to_block(50); + + // finalize the crowdloan + let crowdloan_id: CrowdloanId = 0; + assert_ok!(Crowdloan::finalize( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + + // try update the min contribution + let new_min_contribution: BalanceOf = 20; + assert_err!( + Crowdloan::update_min_contribution( + RuntimeOrigin::signed(creator), + crowdloan_id, + new_min_contribution + ), + pallet_crowdloan::Error::::AlreadyFinalized + ); + }); +} + +#[test] +fn test_update_min_contribution_fails_if_not_creator() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + let crowdloan_id: CrowdloanId = 0; + let new_min_contribution: BalanceOf = 20; + + // try update the min contribution + assert_err!( + Crowdloan::update_min_contribution( + RuntimeOrigin::signed(U256::from(2)), + crowdloan_id, + new_min_contribution + ), + pallet_crowdloan::Error::::InvalidOrigin + ); + }); +} + +#[test] +fn test_update_min_contribution_fails_if_new_min_contribution_is_too_low() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + let crowdloan_id: CrowdloanId = 0; + let new_min_contribution: BalanceOf = 10; + + // try update the min contribution + assert_err!( + Crowdloan::update_min_contribution( + RuntimeOrigin::signed(creator), + crowdloan_id, + new_min_contribution + ), + pallet_crowdloan::Error::::MinimumContributionTooLow + ); + }); +} + +#[test] +fn test_update_end_succeeds() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + let crowdloan_id: CrowdloanId = 0; + let new_end: BlockNumberFor = 60; + + // update the end + assert_ok!(Crowdloan::update_end( + RuntimeOrigin::signed(creator), + crowdloan_id, + new_end + )); + + // ensure the end is updated + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.end == new_end) + ); + }); +} + +#[test] +fn test_update_end_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::update_end(RuntimeOrigin::none(), crowdloan_id, 60), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::update_end(RuntimeOrigin::root(), crowdloan_id, 60), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_update_end_fails_if_crowdloan_does_not_exist() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::update_end(RuntimeOrigin::signed(U256::from(1)), crowdloan_id, 60), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[test] +fn test_update_end_fails_if_crowdloan_has_been_finalized() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + let crowdloan_id: CrowdloanId = 0; + + // some contribution + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some blocks + run_to_block(60); + + // finalize the crowdloan + assert_ok!(Crowdloan::finalize( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + + // try update the end + let new_end: BlockNumberFor = 60; + assert_err!( + Crowdloan::update_end(RuntimeOrigin::signed(creator), crowdloan_id, new_end), + pallet_crowdloan::Error::::AlreadyFinalized + ); + }); +} + +#[test] +fn test_update_end_fails_if_not_creator() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + let crowdloan_id: CrowdloanId = 0; + let new_end: BlockNumberFor = 60; + + // try update the end + assert_err!( + Crowdloan::update_end(RuntimeOrigin::signed(U256::from(2)), crowdloan_id, new_end), + pallet_crowdloan::Error::::InvalidOrigin + ); + }); +} + +#[test] +fn test_update_end_fails_if_new_end_is_in_past() { + TestState::default() + .with_block_number(50) + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 100; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + let crowdloan_id: CrowdloanId = 0; + let new_end: BlockNumberFor = 40; + + // try update the end to a past block number + assert_err!( + Crowdloan::update_end(RuntimeOrigin::signed(creator), crowdloan_id, new_end), + pallet_crowdloan::Error::::CannotEndInPast + ); + }); +} + +#[test] +fn test_update_end_fails_if_block_duration_is_too_short() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // run some blocks + run_to_block(50); + + let crowdloan_id: CrowdloanId = 0; + let new_end: BlockNumberFor = 51; + + // try update the end to a block number that is too long + assert_err!( + Crowdloan::update_end(RuntimeOrigin::signed(creator), crowdloan_id, new_end), + pallet_crowdloan::Error::::BlockDurationTooShort + ); + }); +} + +#[test] +fn test_update_end_fails_if_block_duration_is_too_long() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + let crowdloan_id: CrowdloanId = 0; + let new_end: BlockNumberFor = 1000; + + // try update the end to a block number that is too long + assert_err!( + Crowdloan::update_end(RuntimeOrigin::signed(creator), crowdloan_id, new_end), + pallet_crowdloan::Error::::BlockDurationTooLong + ); + }); +} + +#[test] +fn test_update_cap_succeeds() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // try update the cap + let crowdloan_id: CrowdloanId = 0; + let new_cap: BalanceOf = 200; + assert_ok!(Crowdloan::update_cap( + RuntimeOrigin::signed(creator), + crowdloan_id, + new_cap + )); + + // ensure the cap is updated + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.cap == new_cap) + ); + }); +} + +#[test] +fn test_update_cap_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::update_cap(RuntimeOrigin::none(), crowdloan_id, 200), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::update_cap(RuntimeOrigin::root(), crowdloan_id, 200), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_update_cap_fails_if_crowdloan_does_not_exist() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::update_cap(RuntimeOrigin::signed(U256::from(1)), crowdloan_id, 200), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[test] +fn test_update_cap_fails_if_crowdloan_has_been_finalized() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // some contribution + let crowdloan_id: CrowdloanId = 0; + let contributor: AccountOf = U256::from(2); + let amount: BalanceOf = 50; + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + + // run some blocks + run_to_block(60); + + // finalize the crowdloan + let crowdloan_id: CrowdloanId = 0; + assert_ok!(Crowdloan::finalize( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + + // try update the cap + let new_cap: BalanceOf = 200; + assert_err!( + Crowdloan::update_cap(RuntimeOrigin::signed(creator), crowdloan_id, new_cap), + pallet_crowdloan::Error::::AlreadyFinalized + ); + }); +} + +#[test] +fn test_update_cap_fails_if_not_creator() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // try update the cap + let crowdloan_id: CrowdloanId = 0; + let new_cap: BalanceOf = 200; + assert_err!( + Crowdloan::update_cap(RuntimeOrigin::signed(U256::from(2)), crowdloan_id, new_cap), + pallet_crowdloan::Error::::InvalidOrigin + ); + }); +} + +#[test] +fn test_update_cap_fails_if_new_cap_is_too_low() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // try update the cap + let crowdloan_id: CrowdloanId = 0; + let new_cap: BalanceOf = 50; + assert_err!( + Crowdloan::update_cap(RuntimeOrigin::signed(creator), crowdloan_id, new_cap), + pallet_crowdloan::Error::::CapTooLow + ); + }); +} From 9316c28911f529c3370b0a33dd3eefd53a046285 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 18 Apr 2025 17:24:23 +0200 Subject: [PATCH 55/70] rename MinimumContribution to AbsoluteMinimumContribution --- runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4f0a1e11b2..4a212faca0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1403,7 +1403,7 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall { parameter_types! { pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); pub const MinimumDeposit: Balance = 10_000_000_000; // 10 TAO - pub const MinimumContribution: Balance = 100_000_000; // 0.1 TAO + pub const AbsoluteMinimumContribution: Balance = 100_000_000; // 0.1 TAO pub const MinimumBlockDuration: BlockNumber = if cfg!(feature = "fast-blocks") { 50 } else { @@ -1425,7 +1425,7 @@ impl pallet_crowdloan::Config for Runtime { type WeightInfo = pallet_crowdloan::weights::SubstrateWeight; type Preimages = Preimage; type MinimumDeposit = MinimumDeposit; - type MinimumContribution = MinimumContribution; + type AbsoluteMinimumContribution = AbsoluteMinimumContribution; type MinimumBlockDuration = MinimumBlockDuration; type MaximumBlockDuration = MaximumBlockDuration; type RefundContributorsLimit = RefundContributorsLimit; From 1b6087d806b9985e39cd9eef28f0e6e17b4aa40d Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 21 Apr 2025 12:20:37 +0200 Subject: [PATCH 56/70] fix benchmarking + added dissolve test + rework code --- pallets/crowdloan/src/benchmarking.rs | 117 ++++++++++++++------------ pallets/crowdloan/src/lib.rs | 101 ++++++++++++---------- pallets/crowdloan/src/tests.rs | 58 +++++++++++-- 3 files changed, 174 insertions(+), 102 deletions(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 030d2fb620..6353912fb6 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -32,6 +32,7 @@ mod benchmarks { fn create() { let creator: T::AccountId = account::("creator", 0, SEED); let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); let cap = deposit + deposit; let now = frame_system::Pallet::::block_number(); let end = now + T::MaximumBlockDuration::get(); @@ -44,10 +45,11 @@ mod benchmarks { _( RawOrigin::Signed(creator.clone()), deposit, + min_contribution, cap, end, - Some(target_address.clone()), call.clone(), + Some(target_address.clone()), ); // ensure the crowdloan is stored correctly @@ -58,6 +60,7 @@ mod benchmarks { Some(CrowdloanInfo { creator: creator.clone(), deposit, + min_contribution, cap, end, funds_account: funds_account.clone(), @@ -97,6 +100,7 @@ mod benchmarks { // create a crowdloan let creator: T::AccountId = account::("creator", 0, SEED); let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); let cap = deposit + deposit; let now = frame_system::Pallet::::block_number(); let end = now + T::MaximumBlockDuration::get(); @@ -107,15 +111,16 @@ mod benchmarks { let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, + min_contribution, cap, end, - Some(target_address.clone()), call.clone(), + Some(target_address.clone()), ); // setup contributor let contributor: T::AccountId = account::("contributor", 0, SEED); - let amount: BalanceOf = T::MinimumContribution::get(); + let amount: BalanceOf = min_contribution; let crowdloan_id: CrowdloanId = 0; let _ = CurrencyOf::::set_balance(&contributor, amount); @@ -152,6 +157,7 @@ mod benchmarks { // create a crowdloan let creator: T::AccountId = account::("creator", 0, SEED); let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); let cap = deposit + deposit; let now = frame_system::Pallet::::block_number(); let end = now + T::MaximumBlockDuration::get(); @@ -162,15 +168,16 @@ mod benchmarks { let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, + min_contribution, cap, end, - Some(target_address.clone()), call.clone(), + Some(target_address.clone()), ); // create contribution let contributor: T::AccountId = account::("contributor", 0, SEED); - let amount: BalanceOf = T::MinimumContribution::get(); + let amount: BalanceOf = min_contribution; let crowdloan_id: CrowdloanId = 0; let _ = CurrencyOf::::set_balance(&contributor, amount); let _ = Pallet::::contribute( @@ -212,10 +219,11 @@ mod benchmarks { } #[benchmark] - fn refund(k: Linear<3, { T::RefundContributorsLimit::get() }>) { + fn finalize() { // create a crowdloan let creator: T::AccountId = account::("creator", 0, SEED); let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); let cap = deposit + deposit; let now = frame_system::Pallet::::block_number(); let end = now + T::MaximumBlockDuration::get(); @@ -226,26 +234,23 @@ mod benchmarks { let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, + min_contribution, cap, end, + call.clone(), Some(target_address.clone()), - call, ); + // create contribution fullfilling the cap let crowdloan_id: CrowdloanId = 0; - let amount: BalanceOf = T::MinimumContribution::get(); - // create the worst case count of contributors k to be refunded minus the creator - // who is already a contributor - let contributors = k - 1; - for i in 0..contributors { - let contributor: T::AccountId = account::("contributor", i, SEED); - let _ = CurrencyOf::::set_balance(&contributor, amount); - let _ = Pallet::::contribute( - RawOrigin::Signed(contributor.clone()).into(), - crowdloan_id, - amount, - ); - } + let contributor: T::AccountId = account::("contributor", 0, SEED); + let amount: BalanceOf = cap - deposit; + let _ = CurrencyOf::::set_balance(&contributor, amount); + let _ = Pallet::::contribute( + RawOrigin::Signed(contributor.clone()).into(), + crowdloan_id, + amount, + ); // run to the end of the contribution period frame_system::Pallet::::set_block_number(end); @@ -253,31 +258,20 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Signed(creator.clone()), crowdloan_id); - // ensure the creator has been refunded and the contributions is removed - assert_eq!(CurrencyOf::::balance(&creator), deposit); - assert_eq!(Contributions::::get(crowdloan_id, &creator), None); - // ensure each contributor has been refunded and the contributions is removed - for i in 0..contributors { - let contributor: T::AccountId = account::("contributor", i, SEED); - assert_eq!(CurrencyOf::::balance(&contributor), amount); - assert_eq!(Contributions::::get(crowdloan_id, &contributor), None); - } - // ensure the crowdloan account has been deducted the contributions - assert_eq!( - CurrencyOf::::balance(&Pallet::::funds_account(crowdloan_id)), - 0 - ); - // ensure the raised amount is updated correctly - assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == 0)); + // ensure the target address has received the raised amount + assert_eq!(CurrencyOf::::balance(&target_address), deposit + amount); + // ensure the crowdloan has been finalized + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.finalized)); // ensure the event is emitted - assert_last_event::(Event::::AllRefunded { crowdloan_id }.into()); + assert_last_event::(Event::::Finalized { crowdloan_id }.into()); } #[benchmark] - fn finalize() { + fn refund(k: Linear<3, { T::RefundContributorsLimit::get() }>) { // create a crowdloan let creator: T::AccountId = account::("creator", 0, SEED); let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); let cap = deposit + deposit; let now = frame_system::Pallet::::block_number(); let end = now + T::MaximumBlockDuration::get(); @@ -288,22 +282,27 @@ mod benchmarks { let _ = Pallet::::create( RawOrigin::Signed(creator.clone()).into(), deposit, + min_contribution, cap, end, - Some(target_address.clone()), call, + Some(target_address.clone()), ); - // create contribution fullfilling the cap let crowdloan_id: CrowdloanId = 0; - let contributor: T::AccountId = account::("contributor", 0, SEED); - let amount: BalanceOf = cap - deposit; - let _ = CurrencyOf::::set_balance(&contributor, amount); - let _ = Pallet::::contribute( - RawOrigin::Signed(contributor.clone()).into(), - crowdloan_id, - amount, - ); + let amount: BalanceOf = min_contribution; + // create the worst case count of contributors k to be refunded minus the creator + // who is already a contributor + let contributors = k - 1; + for i in 0..contributors { + let contributor: T::AccountId = account::("contributor", i, SEED); + let _ = CurrencyOf::::set_balance(&contributor, amount); + let _ = Pallet::::contribute( + RawOrigin::Signed(contributor.clone()).into(), + crowdloan_id, + amount, + ); + } // run to the end of the contribution period frame_system::Pallet::::set_block_number(end); @@ -311,12 +310,24 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Signed(creator.clone()), crowdloan_id); - // ensure the target address has received the raised amount - assert_eq!(CurrencyOf::::balance(&target_address), deposit + amount); - // ensure the crowdloan has been finalized - assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.finalized)); + // ensure the creator has been refunded and the contributions is removed + assert_eq!(CurrencyOf::::balance(&creator), deposit); + assert_eq!(Contributions::::get(crowdloan_id, &creator), None); + // ensure each contributor has been refunded and the contributions is removed + for i in 0..contributors { + let contributor: T::AccountId = account::("contributor", i, SEED); + assert_eq!(CurrencyOf::::balance(&contributor), amount); + assert_eq!(Contributions::::get(crowdloan_id, &contributor), None); + } + // ensure the crowdloan account has been deducted the contributions + assert_eq!( + CurrencyOf::::balance(&Pallet::::funds_account(crowdloan_id)), + 0 + ); + // ensure the raised amount is updated correctly + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.raised == 0)); // ensure the event is emitted - assert_last_event::(Event::::Finalized { crowdloan_id }.into()); + assert_last_event::(Event::::AllRefunded { crowdloan_id }.into()); } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 09ea8ce4ef..3bcea6ce0b 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -18,7 +18,7 @@ use frame_support::{ traits::{AccountIdConversion, Dispatchable, Zero}, }, traits::{ - Bounded, Get, IsSubType, QueryPreimage, StorePreimage, fungible, fungible::*, + Bounded, Defensive, Get, IsSubType, QueryPreimage, StorePreimage, fungible, fungible::*, tokens::Preservation, }, }; @@ -162,11 +162,6 @@ pub mod pallet { #[pallet::storage] pub type CurrentCrowdloanId = StorageValue<_, CrowdloanId, OptionQuery>; -/// Scheduled for dissolution - #[pallet::storage] - pub type CrowdloansToDissolve = - StorageMap<_, Twox64Concat, CrowdloanId, CrowdloanInfoOf, OptionQuery>; - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -195,6 +190,8 @@ pub mod pallet { AllRefunded { crowdloan_id: CrowdloanId }, /// A crowdloan was finalized, funds were transferred and the call was dispatched. Finalized { crowdloan_id: CrowdloanId }, +/// A crowdloan was dissolved. + Dissolved { crowdloan_id: CrowdloanId }, } #[pallet::error] @@ -237,6 +234,8 @@ pub mod pallet { Underflow, /// Call to dispatch was not found in the preimage storage. CallUnavailable, +/// The crowdloan is not ready to be dissolved, it still has contributions. + NotReadyToDissolve, } #[pallet::call] @@ -543,28 +542,6 @@ contributor, Ok(()) } - /// Dissolve a crowdloan and schedule for refund. - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::finalize())] - pub fn dissolve( - origin: OriginFor, - #[pallet::compact] crowdloan_id: CrowdloanId, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; - ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); - - // Only the creator can dissolve the crowdloan - ensure!(who == crowdloan.creator, Error::::InvalidOrigin); - - // Mark for dissolution, will be removed in the on_idle block. - CrowdloansToDissolve::::insert(crowdloan_id, crowdloan); - Crowdloans::::remove(crowdloan_id); - - Ok(()) - } - /// Refund a failed crowdloan. /// /// The call will try to refund all contributors up to the limit defined by the `RefundContributorsLimit`. @@ -580,10 +557,14 @@ contributor, origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, ) -> DispatchResultWithPostInfo { +let now = frame_system::Pallet::::block_number(); ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; - Self::ensure_crowdloan_failed(&crowdloan)?; + + // Ensure the crowdloan has ended and is not finalized +ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); let mut refunded_contributors: Vec = vec![]; let mut refund_count = 0; @@ -627,9 +608,41 @@ contributor, } } - /// Update min contribution +/// Dissolve a crowdloan and schedule for refund. + #[pallet::call_index(4)] + // #[pallet::weight(T::WeightInfo::finalize())] + pub fn dissolve( + origin: OriginFor, + #[pallet::compact] crowdloan_id: CrowdloanId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; + ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + + // Only the creator can dissolve the crowdloan + ensure!(who == crowdloan.creator, Error::::InvalidOrigin); + // It can only be dissolved if the raised amount is 0, meaning + // there is no contributions or every contribution has been refunded + ensure!(crowdloan.raised == 0, Error::::NotReadyToDissolve); + + // Remove the crowdloan + let _ = frame_system::Pallet::::dec_providers(&crowdloan.funds_account).defensive(); + Crowdloans::::remove(crowdloan_id); + + Self::deposit_event(Event::::Dissolved { crowdloan_id }); + Ok(()) + } + + /// Update the minimum contribution of a non-finalized crowdloan. + /// + /// The dispatch origin for this call must be _Signed_ and must be the creator of the crowdloan. + /// + /// Parameters: + /// - `crowdloan_id`: The id of the crowdloan to update the minimum contribution of. + /// - `new_min_contribution`: The new minimum contribution. #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::finalize())] +// #[pallet::weight(T::WeightInfo::finalize())] pub fn update_min_contribution( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, @@ -655,7 +668,13 @@ contributor, Ok(()) } - /// Update end + /// Update the end block of a non-finalized crowdloan. + /// + /// The dispatch origin for this call must be _Signed_ and must be the creator of the crowdloan. + /// + /// Parameters: + /// - `crowdloan_id`: The id of the crowdloan to update the end block of. + /// - `new_end`: The new end block. #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::finalize())] pub fn update_end( @@ -680,7 +699,13 @@ contributor, Ok(()) } - /// Update cap + /// Update the cap of a non-finalized crowdloan. + /// + /// The dispatch origin for this call must be _Signed_ and must be the creator of the crowdloan. + /// + /// Parameters: + /// - `crowdloan_id`: The id of the crowdloan to update the cap of. + /// - `new_cap`: The new cap. #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::finalize())] pub fn update_cap( @@ -717,16 +742,6 @@ impl Pallet { Crowdloans::::get(crowdloan_id).ok_or(Error::::InvalidCrowdloanId) } - // A crowdloan is considered to have failed if it has ended, has not raised the cap and - // has not been finalized. - fn ensure_crowdloan_failed(crowdloan: &CrowdloanInfoOf) -> Result<(), Error> { - let now = frame_system::Pallet::::block_number(); - ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); - ensure!(crowdloan.raised < crowdloan.cap, Error::::CapRaised); - ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); - Ok(()) - } - // Ensure the provided end block is after the current block and the duration is // between the minimum and maximum block duration fn ensure_valid_end(now: BlockNumberFor, end: BlockNumberFor) -> Result<(), Error> { diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index ed259baee4..656573c234 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1360,7 +1360,7 @@ min_contribution, } #[test] -fn test_dissolve_succeeds_for_a_running_crowdloan() { +fn test_dissolve_succeeds() { TestState::default() .with_balance(U256::from(1), 100) .build_and_execute(|| { @@ -1381,6 +1381,16 @@ fn test_dissolve_succeeds_for_a_running_crowdloan() { None, )); + // run some blocks past end + run_to_block(60); + + // refund the contributions + let crowdloan_id: CrowdloanId = 0; + assert_ok!(Crowdloan::refund( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + // dissolve the crowdloan let crowdloan_id: CrowdloanId = 0; assert_ok!(Crowdloan::dissolve( @@ -1388,11 +1398,14 @@ fn test_dissolve_succeeds_for_a_running_crowdloan() { crowdloan_id )); - // ensure the crowdloan is marked for dissolution - assert!(pallet_crowdloan::CrowdloansToDissolve::::get(crowdloan_id).is_some()); - // ensure the crowdloan is removed from the crowdloans map assert!(pallet_crowdloan::Crowdloans::::get(crowdloan_id).is_none()); + + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::Dissolved { crowdloan_id }.into() + ) }); } @@ -1481,8 +1494,7 @@ fn test_dissolve_fails_if_crowdloan_has_been_finalized() { fn test_dissolve_fails_if_origin_is_not_creator() { TestState::default() .with_balance(U256::from(1), 100) - .with_balance(U256::from(2), 100) - .build_and_execute(|| { + .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let min_contribution: BalanceOf = 10; @@ -1513,6 +1525,40 @@ fn test_dissolve_fails_if_origin_is_not_creator() { }); } +#[test] +fn test_dissolve_fails_if_not_everyone_has_been_refunded() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + // create a crowdloan + let creator: AccountOf = U256::from(1); + let deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 100; + let end: BlockNumberFor = 50; + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // run some blocks past end + run_to_block(10); + + // try to dissolve the crowdloan + let crowdloan_id = 0; + assert_err!( + Crowdloan::dissolve(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_crowdloan::Error::::NotReadyToDissolve + ); + }); +} + #[test] fn test_update_min_contribution_succeeds() { TestState::default() From 86d31b0efeb1ffcb9e5fdd9b9b0555a50ce122b5 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 21 Apr 2025 12:25:33 +0200 Subject: [PATCH 57/70] fix refund tests --- pallets/crowdloan/src/tests.rs | 174 +++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 656573c234..80300a0c99 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1359,6 +1359,180 @@ min_contribution, }); } +#[test] +fn test_refund_succeeds() { + TestState::default() + .with_balance(U256::from(1), 100) + .with_balance(U256::from(2), 100) + .with_balance(U256::from(3), 100) + .with_balance(U256::from(4), 100) + .with_balance(U256::from(5), 100) + .with_balance(U256::from(6), 100) + .with_balance(U256::from(7), 100) + .build_and_execute(|| { + // create a crowdloan + let creator: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 400; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + initial_deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // run some blocks + run_to_block(10); + + // make 6 contributions to reach 350 raised amount (initial deposit + contributions) + let crowdloan_id: CrowdloanId = 0; + let amount: BalanceOf = 50; + for i in 2..8 { + let contributor: AccountOf = U256::from(i); + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + } + + // run some more blocks past the end of the contribution period + run_to_block(60); + + // first round of refund + assert_ok!(Crowdloan::refund( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + + // ensure the crowdloan account has the correct amount + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); + assert_eq!(Balances::free_balance(funds_account), 350 - 5 * amount); + // ensure raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 350 - 5 * amount) + ); + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::PartiallyRefunded { crowdloan_id }.into() + ); + + // run some more blocks + run_to_block(70); + + // second round of refund + assert_ok!(Crowdloan::refund( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + + // ensure the crowdloan account has the correct amount + assert_eq!( + pallet_balances::Pallet::::free_balance(funds_account), + 0 + ); + // ensure the raised amount is updated correctly + assert!( + pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.raised == 0) + ); + + // ensure creator has the correct amount + assert_eq!(pallet_balances::Pallet::::free_balance(creator), 100); + + // ensure each contributor has been refunded and removed from the crowdloan + for i in 2..8 { + let contributor: AccountOf = U256::from(i); + assert_eq!( + pallet_balances::Pallet::::free_balance(contributor), + 100 + ); + assert_eq!( + pallet_crowdloan::Contributions::::get(crowdloan_id, contributor), + None, + ); + } + + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::AllRefunded { crowdloan_id }.into() + ); + }) +} + +#[test] +fn test_refund_fails_if_bad_origin() { + TestState::default().build_and_execute(|| { + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::refund(RuntimeOrigin::none(), crowdloan_id), + DispatchError::BadOrigin + ); + + assert_err!( + Crowdloan::refund(RuntimeOrigin::root(), crowdloan_id), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_refund_fails_if_crowdloan_does_not_exist() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let crowdloan_id: CrowdloanId = 0; + + assert_err!( + Crowdloan::refund(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[test] +fn test_refund_fails_if_crowdloan_has_not_ended() { + TestState::default() + .with_balance(U256::from(1), 100) + .build_and_execute(|| { + // create a crowdloan + let creator: AccountOf = U256::from(1); + let initial_deposit: BalanceOf = 50; + let min_contribution: BalanceOf = 10; + let cap: BalanceOf = 300; + let end: BlockNumberFor = 50; + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + initial_deposit, + min_contribution, + cap, + end, + noop_call(), + None, + )); + + // run some blocks + run_to_block(10); + + // try to refund + let crowdloan_id: CrowdloanId = 0; + assert_err!( + Crowdloan::refund(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_crowdloan::Error::::ContributionPeriodNotEnded + ); + }); +} + #[test] fn test_dissolve_succeeds() { TestState::default() From 4b8c87c9bf3705a9833a09b35b7607cc445374e7 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 21 Apr 2025 13:51:58 +0200 Subject: [PATCH 58/70] add missing events + add dissolve benchmark --- pallets/crowdloan/src/benchmarking.rs | 168 ++++++++++++++++++- pallets/crowdloan/src/lib.rs | 77 ++++++--- pallets/crowdloan/src/mock.rs | 12 ++ pallets/crowdloan/src/tests.rs | 223 +++++++++++++++----------- 4 files changed, 355 insertions(+), 125 deletions(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 6353912fb6..44cdae806b 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -8,7 +8,7 @@ use crate::{BalanceOf, CrowdloanId, CrowdloanInfo, CurrencyOf, pallet::*}; use frame_benchmarking::{account, v2::*}; use frame_support::traits::{Get, StorePreimage, fungible::*}; -use frame_system::RawOrigin; +use frame_system::{RawOrigin, pallet_prelude::BlockNumberFor}; extern crate alloc; @@ -330,5 +330,169 @@ mod benchmarks { assert_last_event::(Event::::AllRefunded { crowdloan_id }.into()); } - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); + #[benchmark] + fn dissolve() { + // create a crowdloan + let creator: T::AccountId = account::("creator", 0, SEED); + let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); + let cap = deposit + deposit; + let now = frame_system::Pallet::::block_number(); + let end = now + T::MaximumBlockDuration::get(); + let target_address: T::AccountId = account::("target_address", 0, SEED); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::set_balance(&creator, deposit); + let _ = Pallet::::create( + RawOrigin::Signed(creator.clone()).into(), + deposit, + min_contribution, + cap, + end, + call, + Some(target_address.clone()), + ); + + // run to the end of the contribution period + frame_system::Pallet::::set_block_number(end); + + // refund the contributions + let crowdloan_id: CrowdloanId = 0; + let _ = Pallet::::refund(RawOrigin::Signed(creator.clone()).into(), crowdloan_id); + + #[extrinsic_call] + _(RawOrigin::Signed(creator.clone()), crowdloan_id); + + // ensure the crowdloan has been dissolved + assert!(Crowdloans::::get(crowdloan_id).is_none()); + // ensure the event is emitted + assert_last_event::(Event::::Dissolved { crowdloan_id }.into()); + } + + #[benchmark] + fn update_min_contribution() { + // create a crowdloan + let creator: T::AccountId = account::("creator", 0, SEED); + let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); + let cap = deposit + deposit; + let end = frame_system::Pallet::::block_number() + T::MaximumBlockDuration::get(); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::set_balance(&creator, deposit); + let _ = Pallet::::create( + RawOrigin::Signed(creator.clone()).into(), + deposit, + min_contribution, + cap, + end, + call, + None, + ); + + let crowdloan_id: CrowdloanId = 0; + let new_min_contribution: BalanceOf = min_contribution + min_contribution; + + #[extrinsic_call] + _( + RawOrigin::Signed(creator.clone()), + crowdloan_id, + new_min_contribution, + ); + + // ensure the min contribution is updated correctly + assert!( + Crowdloans::::get(crowdloan_id) + .is_some_and(|c| c.min_contribution == new_min_contribution) + ); + // ensure the event is emitted + assert_last_event::( + Event::::MinContributionUpdated { + crowdloan_id, + new_min_contribution, + } + .into(), + ); + } + + #[benchmark] + fn update_end() { + // create a crowdloan + let creator: T::AccountId = account::("creator", 0, SEED); + let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); + let cap = deposit + deposit; + let now = frame_system::Pallet::::block_number(); + let end = now + T::MinimumBlockDuration::get(); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::set_balance(&creator, deposit); + let _ = Pallet::::create( + RawOrigin::Signed(creator.clone()).into(), + deposit, + min_contribution, + cap, + end, + call, + None, + ); + + let crowdloan_id: CrowdloanId = 0; + let new_end: BlockNumberFor = now + T::MaximumBlockDuration::get(); + + #[extrinsic_call] + _(RawOrigin::Signed(creator.clone()), crowdloan_id, new_end); + + // ensure the end is updated correctly + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.end == new_end)); + // ensure the event is emitted + assert_last_event::( + Event::::EndUpdated { + crowdloan_id, + new_end, + } + .into(), + ); + } + + #[benchmark] + fn update_cap() { + // create a crowdloan + let creator: T::AccountId = account::("creator", 0, SEED); + let deposit = T::MinimumDeposit::get(); + let min_contribution = T::AbsoluteMinimumContribution::get(); + let cap = deposit + deposit; + let end = frame_system::Pallet::::block_number() + T::MaximumBlockDuration::get(); + let call: Box<::RuntimeCall> = + Box::new(frame_system::Call::::remark { remark: vec![] }.into()); + let _ = CurrencyOf::::set_balance(&creator, deposit); + let _ = Pallet::::create( + RawOrigin::Signed(creator.clone()).into(), + deposit, + min_contribution, + cap, + end, + call, + None, + ); + + let crowdloan_id: CrowdloanId = 0; + let new_cap: BalanceOf = cap + cap; + + #[extrinsic_call] + _(RawOrigin::Signed(creator.clone()), crowdloan_id, new_cap); + + // ensure the cap is updated correctly + assert!(Crowdloans::::get(crowdloan_id).is_some_and(|c| c.cap == new_cap)); + // ensure the event is emitted + assert_last_event::( + Event::::CapUpdated { + crowdloan_id, + new_cap, + } + .into(), + ); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 3bcea6ce0b..d37ad3814c 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -53,7 +53,7 @@ pub struct CrowdloanInfo { pub creator: AccountId, /// The initial deposit of the crowdloan from the creator. pub deposit: Balance, -/// Minimum contribution to the crowdloan. + /// Minimum contribution to the crowdloan. pub min_contribution: Balance, /// The end block of the crowdloan. pub end: BlockNumber, @@ -190,8 +190,23 @@ pub mod pallet { AllRefunded { crowdloan_id: CrowdloanId }, /// A crowdloan was finalized, funds were transferred and the call was dispatched. Finalized { crowdloan_id: CrowdloanId }, -/// A crowdloan was dissolved. + /// A crowdloan was dissolved. Dissolved { crowdloan_id: CrowdloanId }, + /// The minimum contribution was updated. + MinContributionUpdated { + crowdloan_id: CrowdloanId, + new_min_contribution: BalanceOf, + }, + /// The end was updated. + EndUpdated { + crowdloan_id: CrowdloanId, + new_end: BlockNumberFor, + }, + /// The cap was updated. + CapUpdated { + crowdloan_id: CrowdloanId, + new_cap: BalanceOf, + }, } #[pallet::error] @@ -200,7 +215,7 @@ pub mod pallet { DepositTooLow, /// The crowdloan cap is too low. CapTooLow, -/// The minimum contribution is too low. + /// The minimum contribution is too low. MinimumContributionTooLow, /// The crowdloan cannot end in the past. CannotEndInPast, @@ -234,7 +249,7 @@ pub mod pallet { Underflow, /// Call to dispatch was not found in the preimage storage. CallUnavailable, -/// The crowdloan is not ready to be dissolved, it still has contributions. + /// The crowdloan is not ready to be dissolved, it still has contributions. NotReadyToDissolve, } @@ -252,11 +267,11 @@ pub mod pallet { /// /// Parameters: /// - `deposit`: The initial deposit from the creator. -/// - `min_contribution`: The minimum contribution required to contribute to the crowdloan. + /// - `min_contribution`: The minimum contribution required to contribute to the crowdloan. /// - `cap`: The maximum amount of funds that can be raised. /// - `end`: The block number at which the crowdloan will end. - /// - `call`: The call to dispatch when the crowdloan is finalized. -/// - `target_address`: The address to transfer the raised funds to if provided. + /// - `call`: The call to dispatch when the crowdloan is finalized. + /// - `target_address`: The address to transfer the raised funds to if provided. #[pallet::call_index(0)] #[pallet::weight({ let di = call.get_dispatch_info(); @@ -270,11 +285,11 @@ pub mod pallet { pub fn create( origin: OriginFor, #[pallet::compact] deposit: BalanceOf, -#[pallet::compact] min_contribution: BalanceOf, + #[pallet::compact] min_contribution: BalanceOf, #[pallet::compact] cap: BalanceOf, #[pallet::compact] end: BlockNumberFor, - call: Box<::RuntimeCall>, -target_address: Option, + call: Box<::RuntimeCall>, + target_address: Option, ) -> DispatchResult { let creator = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); @@ -310,7 +325,7 @@ target_address: Option, let crowdloan = CrowdloanInfo { creator: creator.clone(), deposit, -min_contribution, + min_contribution, end, cap, funds_account, @@ -414,8 +429,8 @@ min_contribution, Crowdloans::::insert(crowdloan_id, &crowdloan); Self::deposit_event(Event::::Contributed { - crowdloan_id, -contributor, + crowdloan_id, + contributor, amount, }); @@ -551,19 +566,19 @@ contributor, /// /// Parameters: /// - `crowdloan_id`: The id of the crowdloan to refund. - #[pallet::call_index(5)] + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::refund(T::RefundContributorsLimit::get()))] pub fn refund( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, ) -> DispatchResultWithPostInfo { -let now = frame_system::Pallet::::block_number(); + let now = frame_system::Pallet::::block_number(); ensure_signed(origin)?; let mut crowdloan = Self::ensure_crowdloan_exists(crowdloan_id)?; - + // Ensure the crowdloan has ended and is not finalized -ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); + ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); let mut refunded_contributors: Vec = vec![]; @@ -608,9 +623,9 @@ ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); } } -/// Dissolve a crowdloan and schedule for refund. - #[pallet::call_index(4)] - // #[pallet::weight(T::WeightInfo::finalize())] + /// Dissolve a crowdloan and schedule for refund. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::dissolve())] pub fn dissolve( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, @@ -642,11 +657,11 @@ ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); /// - `crowdloan_id`: The id of the crowdloan to update the minimum contribution of. /// - `new_min_contribution`: The new minimum contribution. #[pallet::call_index(6)] -// #[pallet::weight(T::WeightInfo::finalize())] + #[pallet::weight(T::WeightInfo::update_min_contribution())] pub fn update_min_contribution( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, -#[pallet::compact] new_min_contribution: BalanceOf, + #[pallet::compact] new_min_contribution: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -656,7 +671,7 @@ ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); // Only the creator can update the min contribution. ensure!(who == crowdloan.creator, Error::::InvalidOrigin); -// The new min contribution should be greater than absolute minimum contribution. + // The new min contribution should be greater than absolute minimum contribution. ensure!( new_min_contribution > T::AbsoluteMinimumContribution::get(), Error::::MinimumContributionTooLow @@ -665,6 +680,10 @@ ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); crowdloan.min_contribution = new_min_contribution; Crowdloans::::insert(crowdloan_id, &crowdloan); + Self::deposit_event(Event::::MinContributionUpdated { + crowdloan_id, + new_min_contribution, + }); Ok(()) } @@ -676,7 +695,7 @@ ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); /// - `crowdloan_id`: The id of the crowdloan to update the end block of. /// - `new_end`: The new end block. #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::finalize())] + #[pallet::weight(T::WeightInfo::update_end())] pub fn update_end( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, @@ -696,6 +715,10 @@ ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); crowdloan.end = new_end; Crowdloans::::insert(crowdloan_id, &crowdloan); + Self::deposit_event(Event::::EndUpdated { + crowdloan_id, + new_end, + }); Ok(()) } @@ -707,7 +730,7 @@ ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); /// - `crowdloan_id`: The id of the crowdloan to update the cap of. /// - `new_cap`: The new cap. #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::finalize())] + #[pallet::weight(T::WeightInfo::update_cap())] pub fn update_cap( origin: OriginFor, #[pallet::compact] crowdloan_id: CrowdloanId, @@ -728,6 +751,10 @@ ensure!(now >= crowdloan.end, Error::::ContributionPeriodNotEnded); crowdloan.cap = new_cap; Crowdloans::::insert(crowdloan_id, &crowdloan); + Self::deposit_event(Event::::CapUpdated { + crowdloan_id, + new_cap, + }); Ok(()) } } diff --git a/pallets/crowdloan/src/mock.rs b/pallets/crowdloan/src/mock.rs index 43319fe4ff..980b9fa26b 100644 --- a/pallets/crowdloan/src/mock.rs +++ b/pallets/crowdloan/src/mock.rs @@ -76,6 +76,18 @@ impl WeightInfo for TestWeightInfo { fn finalize() -> Weight { Weight::zero() } + fn dissolve() -> Weight { + Weight::zero() + } + fn update_min_contribution() -> Weight { + Weight::zero() + } + fn update_end() -> Weight { + Weight::zero() + } + fn update_cap() -> Weight { + Weight::zero() + } } parameter_types! { diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 80300a0c99..1cee6c9bef 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -15,18 +15,18 @@ fn test_create_succeeds() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; - let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), - deposit, + deposit, min_contribution, cap, end, - noop_call(), -None, + noop_call(), + None, )); let crowdloan_id = 0; @@ -38,7 +38,7 @@ None, Some(CrowdloanInfo { creator, deposit, -min_contribution, + min_contribution, cap, end, funds_account, @@ -86,33 +86,33 @@ min_contribution, fn test_create_fails_if_bad_origin() { TestState::default().build_and_execute(|| { let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_err!( Crowdloan::create( -RuntimeOrigin::none(), -deposit, + RuntimeOrigin::none(), + deposit, min_contribution, -cap, -end, -noop_call(), + cap, + end, + noop_call(), None -), + ), DispatchError::BadOrigin ); assert_err!( Crowdloan::create( -RuntimeOrigin::root(), -deposit, + RuntimeOrigin::root(), + deposit, min_contribution, -cap, -end, -noop_call(), + cap, + end, + noop_call(), None -), + ), DispatchError::BadOrigin ); }); @@ -125,7 +125,7 @@ fn test_create_fails_if_deposit_is_too_low() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 20; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; @@ -133,10 +133,10 @@ let min_contribution: BalanceOf = 10; Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None ), pallet_crowdloan::Error::::DepositTooLow @@ -151,7 +151,7 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 40; let end: BlockNumberFor = 50; @@ -159,10 +159,10 @@ let min_contribution: BalanceOf = 10; Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None ), pallet_crowdloan::Error::::CapTooLow @@ -206,7 +206,7 @@ fn test_create_fails_if_end_is_in_the_past() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = current_block_number - 5; @@ -214,10 +214,10 @@ let min_contribution: BalanceOf = 10; Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None ), pallet_crowdloan::Error::::CannotEndInPast @@ -232,7 +232,7 @@ fn test_create_fails_if_block_duration_is_too_short() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 11; @@ -240,10 +240,10 @@ let min_contribution: BalanceOf = 10; Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None ), pallet_crowdloan::Error::::BlockDurationTooShort @@ -258,7 +258,7 @@ fn test_create_fails_if_block_duration_is_too_long() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 1000; @@ -266,10 +266,10 @@ let min_contribution: BalanceOf = 10; Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None ), pallet_crowdloan::Error::::BlockDurationTooLong @@ -284,7 +284,7 @@ fn test_create_fails_if_creator_has_insufficient_balance() { .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 200; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; @@ -292,10 +292,10 @@ let min_contribution: BalanceOf = 10; Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None ), pallet_crowdloan::Error::::InsufficientBalance @@ -313,17 +313,17 @@ fn test_contribute_succeeds() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -423,17 +423,17 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -544,17 +544,17 @@ fn test_contribute_fails_if_contribution_period_ended() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -582,17 +582,17 @@ fn test_contribute_fails_if_cap_has_been_raised() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -628,17 +628,17 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -665,17 +665,17 @@ fn test_contribute_fails_if_contributor_has_insufficient_balance() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -704,17 +704,17 @@ fn test_withdraw_succeeds() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -786,17 +786,17 @@ fn test_withdraw_succeeds_for_another_contributor() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -886,17 +886,17 @@ fn test_withdraw_fails_if_no_contribution_exists() { // create a crowdloan let creator: AccountOf = U256::from(1); let initial_deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 300; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), initial_deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -935,18 +935,18 @@ fn test_finalize_succeeds() { TestState::default() .with_balance(U256::from(1), 100) .with_balance(U256::from(2), 100) - .build_and_execute(|| { + .build_and_execute(|| { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, Box::new(RuntimeCall::TestPallet( @@ -962,15 +962,15 @@ min_contribution, // some contribution let crowdloan_id: CrowdloanId = 0; -let contributor: AccountOf = U256::from(2); + let contributor: AccountOf = U256::from(2); let amount: BalanceOf = 50; - - assert_ok!(Crowdloan::contribute( - RuntimeOrigin::signed(contributor), - crowdloan_id, - amount - )); - + + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + amount + )); + // run some more blocks past the end of the contribution period run_to_block(60); @@ -1015,7 +1015,7 @@ fn test_finalize_succeeds_with_target_address() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); @@ -1023,10 +1023,10 @@ let min_contribution: BalanceOf = 10; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - Box::new(RuntimeCall::TestPallet( + Box::new(RuntimeCall::TestPallet( pallet_test::Call::::set_passed_crowdloan_id {} )), Some(target_address), @@ -1125,17 +1125,17 @@ fn test_finalize_fails_if_not_creator_origin() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - noop_call(), + noop_call(), None )); @@ -1172,19 +1172,19 @@ fn test_finalize_fails_if_crowdloan_has_not_ended() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, -noop_call(), + noop_call(), None, - )); + )); // run some blocks run_to_block(10); @@ -1220,19 +1220,19 @@ fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, -noop_call(), + noop_call(), None, - )); + )); // run some blocks run_to_block(10); @@ -1251,7 +1251,7 @@ noop_call(), // run some more blocks past the end of the contribution period run_to_block(60); - // try finalize the crowdloan + // try finalize the crowdloan assert_err!( Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), pallet_crowdloan::Error::::CapNotRaised @@ -1268,19 +1268,19 @@ fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, -noop_call(), + noop_call(), None, - )); + )); // some contribution let crowdloan_id: CrowdloanId = 0; @@ -1296,7 +1296,7 @@ noop_call(), // run some more blocks past the end of the contribution period run_to_block(60); -// finalize the crowdloan + // finalize the crowdloan assert_ok!(Crowdloan::finalize( RuntimeOrigin::signed(creator), crowdloan_id @@ -1319,17 +1319,17 @@ fn test_finalize_fails_if_call_fails() { // create a crowdloan let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; -let min_contribution: BalanceOf = 10; + let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), deposit, -min_contribution, + min_contribution, cap, end, - Box::new(RuntimeCall::TestPallet( + Box::new(RuntimeCall::TestPallet( pallet_test::Call::::failing_extrinsic {} )), None, @@ -1668,7 +1668,7 @@ fn test_dissolve_fails_if_crowdloan_has_been_finalized() { fn test_dissolve_fails_if_origin_is_not_creator() { TestState::default() .with_balance(U256::from(1), 100) - .build_and_execute(|| { + .build_and_execute(|| { let creator: AccountOf = U256::from(1); let deposit: BalanceOf = 50; let min_contribution: BalanceOf = 10; @@ -1770,6 +1770,15 @@ fn test_update_min_contribution_succeeds() { pallet_crowdloan::Crowdloans::::get(crowdloan_id) .is_some_and(|c| c.min_contribution == new_min_contribution) ); + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::MinContributionUpdated { + crowdloan_id, + new_min_contribution + } + .into() + ); }); } @@ -1970,6 +1979,15 @@ fn test_update_end_succeeds() { pallet_crowdloan::Crowdloans::::get(crowdloan_id) .is_some_and(|c| c.end == new_end) ); + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::EndUpdated { + crowdloan_id, + new_end + } + .into() + ); }); } @@ -2221,6 +2239,15 @@ fn test_update_cap_succeeds() { pallet_crowdloan::Crowdloans::::get(crowdloan_id) .is_some_and(|c| c.cap == new_cap) ); + // ensure the event is emitted + assert_eq!( + last_event(), + pallet_crowdloan::Event::::CapUpdated { + crowdloan_id, + new_cap + } + .into() + ); }); } From 778908ceb8756821ebe0b623732c6d08d1d3342b Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 21 Apr 2025 14:02:05 +0200 Subject: [PATCH 59/70] updated weights --- pallets/crowdloan/src/weights.rs | 224 ++++++++++++++++++++++--------- 1 file changed, 160 insertions(+), 64 deletions(-) diff --git a/pallets/crowdloan/src/weights.rs b/pallets/crowdloan/src/weights.rs index 29c81c041d..67f1c76b21 100644 --- a/pallets/crowdloan/src/weights.rs +++ b/pallets/crowdloan/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for `pallet_crowdloan` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 -//! DATE: 2025-04-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-04-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `Ubuntu-2404-noble-amd64-base`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: `1024` @@ -34,8 +34,12 @@ pub trait WeightInfo { fn create() -> Weight; fn contribute() -> Weight; fn withdraw() -> Weight; - fn refund(k: u32, ) -> Weight; fn finalize() -> Weight; + fn refund(k: u32, ) -> Weight; + fn dissolve() -> Weight; + fn update_min_contribution() -> Weight; + fn update_end() -> Weight; + fn update_cap() -> Weight; } /// Weights for `pallet_crowdloan` using the Substrate node and recommended hardware. @@ -48,48 +52,65 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Crowdloan::Contributions` (r:0 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 40_165_000 picoseconds. - Weight::from_parts(40_837_000, 6148) + // Minimum execution time: 40_335_000 picoseconds. + Weight::from_parts(41_647_000, 6148) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `467` + // Measured: `475` // Estimated: `6148` - // Minimum execution time: 42_819_000 picoseconds. - Weight::from_parts(43_782_000, 6148) + // Minimum execution time: 43_691_000 picoseconds. + Weight::from_parts(44_332_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `427` + // Measured: `435` // Estimated: `6148` - // Minimum execution time: 40_656_000 picoseconds. - Weight::from_parts(40_966_000, 6148) + // Minimum execution time: 40_235_000 picoseconds. + Weight::from_parts(41_117_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) + /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn finalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `6148` + // Minimum execution time: 40_636_000 picoseconds. + Weight::from_parts(41_497_000, 6148) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:6 w:5) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:6 w:6) @@ -97,12 +118,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `k` is `[3, 5]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `452 + k * (45 ±0)` - // Estimated: `3734 + k * (2579 ±0)` - // Minimum execution time: 97_613_000 picoseconds. - Weight::from_parts(17_688_033, 3734) - // Standard Error: 60_926 - .saturating_add(Weight::from_parts(27_400_437, 0).saturating_mul(k.into())) + // Measured: `460 + k * (45 ±0)` + // Estimated: `3742 + k * (2579 ±0)` + // Minimum execution time: 96_831_000 picoseconds. + Weight::from_parts(23_219_468, 3742) + // Standard Error: 128_696 + .saturating_add(Weight::from_parts(26_075_135, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -110,21 +131,50 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:2) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) - /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) - /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) - /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - fn finalize() -> Weight { + fn dissolve() -> Weight { // Proof Size summary in bytes: - // Measured: `367` - // Estimated: `6148` - // Minimum execution time: 40_005_000 picoseconds. - Weight::from_parts(40_867_000, 6148) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Measured: `320` + // Estimated: `3742` + // Minimum execution time: 11_551_000 picoseconds. + Weight::from_parts(12_092_000, 3742) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + fn update_min_contribution() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `3742` + // Minimum execution time: 8_667_000 picoseconds. + Weight::from_parts(8_957_000, 3742) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + fn update_end() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `3742` + // Minimum execution time: 8_776_000 picoseconds. + Weight::from_parts(9_067_000, 3742) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + fn update_cap() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `3742` + // Minimum execution time: 8_546_000 picoseconds. + Weight::from_parts(8_927_000, 3742) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -137,48 +187,65 @@ impl WeightInfo for () { /// Storage: `Crowdloan::Contributions` (r:0 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 40_165_000 picoseconds. - Weight::from_parts(40_837_000, 6148) + // Minimum execution time: 40_335_000 picoseconds. + Weight::from_parts(41_647_000, 6148) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `467` + // Measured: `475` // Estimated: `6148` - // Minimum execution time: 42_819_000 picoseconds. - Weight::from_parts(43_782_000, 6148) + // Minimum execution time: 43_691_000 picoseconds. + Weight::from_parts(44_332_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `427` + // Measured: `435` // Estimated: `6148` - // Minimum execution time: 40_656_000 picoseconds. - Weight::from_parts(40_966_000, 6148) + // Minimum execution time: 40_235_000 picoseconds. + Weight::from_parts(41_117_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) + /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn finalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `6148` + // Minimum execution time: 40_636_000 picoseconds. + Weight::from_parts(41_497_000, 6148) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:6 w:5) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:6 w:6) @@ -186,12 +253,12 @@ impl WeightInfo for () { /// The range of component `k` is `[3, 5]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `452 + k * (45 ±0)` - // Estimated: `3734 + k * (2579 ±0)` - // Minimum execution time: 97_613_000 picoseconds. - Weight::from_parts(17_688_033, 3734) - // Standard Error: 60_926 - .saturating_add(Weight::from_parts(27_400_437, 0).saturating_mul(k.into())) + // Measured: `460 + k * (45 ±0)` + // Estimated: `3742 + k * (2579 ±0)` + // Minimum execution time: 96_831_000 picoseconds. + Weight::from_parts(23_219_468, 3742) + // Standard Error: 128_696 + .saturating_add(Weight::from_parts(26_075_135, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -199,20 +266,49 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(269), added: 2744, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:2) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) - /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) - /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) - /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - fn finalize() -> Weight { + fn dissolve() -> Weight { // Proof Size summary in bytes: - // Measured: `367` - // Estimated: `6148` - // Minimum execution time: 40_005_000 picoseconds. - Weight::from_parts(40_867_000, 6148) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Measured: `320` + // Estimated: `3742` + // Minimum execution time: 11_551_000 picoseconds. + Weight::from_parts(12_092_000, 3742) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + fn update_min_contribution() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `3742` + // Minimum execution time: 8_667_000 picoseconds. + Weight::from_parts(8_957_000, 3742) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + fn update_end() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `3742` + // Minimum execution time: 8_776_000 picoseconds. + Weight::from_parts(9_067_000, 3742) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + fn update_cap() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `3742` + // Minimum execution time: 8_546_000 picoseconds. + Weight::from_parts(8_927_000, 3742) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } \ No newline at end of file From eb2f098f4397c8a76ae8502426bcce66968f46ab Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 21 Apr 2025 14:11:41 +0200 Subject: [PATCH 60/70] fix doc --- pallets/crowdloan/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index d37ad3814c..75ba555974 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -623,7 +623,15 @@ pub mod pallet { } } - /// Dissolve a crowdloan and schedule for refund. + /// Dissolve a crowdloan. + /// + /// The crowdloan will be removed from the storage. + /// All contributions must have been refunded before the crowdloan can be dissolved. + /// + /// The dispatch origin for this call must be _Signed_ and must be the creator of the crowdloan. + /// + /// Parameters: + /// - `crowdloan_id`: The id of the crowdloan to dissolve. #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::dissolve())] pub fn dissolve( @@ -649,7 +657,7 @@ pub mod pallet { Ok(()) } - /// Update the minimum contribution of a non-finalized crowdloan. + /// Update the minimum contribution of a non-finalized crowdloan. /// /// The dispatch origin for this call must be _Signed_ and must be the creator of the crowdloan. /// From e1d16867d458f1cba0978da44b059542584523f5 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 21 Apr 2025 14:14:00 +0200 Subject: [PATCH 61/70] set max refunds to 50 and max block duration to 60 days --- pallets/crowdloan/src/lib.rs | 1 + runtime/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 75ba555974..1674f6d6f3 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -561,6 +561,7 @@ pub mod pallet { /// /// The call will try to refund all contributors up to the limit defined by the `RefundContributorsLimit`. /// If the limit is reached, the call will stop and the crowdloan will be marked as partially refunded. + /// It may be needed to dispatch this call multiple times to refund all contributors. /// /// The dispatch origin for this call must be _Signed_ and doesn't need to be the creator of the crowdloan. /// diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4a212faca0..a80ec97769 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1412,9 +1412,9 @@ parameter_types! { pub const MaximumBlockDuration: BlockNumber = if cfg!(feature = "fast-blocks") { 20000 } else { - 216000 // 30 days maximum (30 * 24 * 60 * 60 / 12) + 432000 // 60 days maximum (60 * 24 * 60 * 60 / 12) }; - pub const RefundContributorsLimit: u32 = 5; + pub const RefundContributorsLimit: u32 = 50; } impl pallet_crowdloan::Config for Runtime { From 1a927e14cbe1a80ec2f0793b7df23420bf500071 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 21 Apr 2025 16:44:45 +0200 Subject: [PATCH 62/70] cargo fmt --- pallets/crowdloan/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 1674f6d6f3..b09fab9c56 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -624,8 +624,8 @@ pub mod pallet { } } - /// Dissolve a crowdloan. - /// + /// Dissolve a crowdloan. + /// /// The crowdloan will be removed from the storage. /// All contributions must have been refunded before the crowdloan can be dissolved. /// From 1d0ac80241e8fa4b86df8a7a14e45208122d2938 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 22 Apr 2025 14:22:11 +0200 Subject: [PATCH 63/70] make call optional --- pallets/crowdloan/src/benchmarking.rs | 28 +++---- pallets/crowdloan/src/lib.rs | 78 +++++++++++------- pallets/crowdloan/src/tests.rs | 113 +++++++++++++------------- 3 files changed, 119 insertions(+), 100 deletions(-) diff --git a/pallets/crowdloan/src/benchmarking.rs b/pallets/crowdloan/src/benchmarking.rs index 44cdae806b..5dab0c1b91 100644 --- a/pallets/crowdloan/src/benchmarking.rs +++ b/pallets/crowdloan/src/benchmarking.rs @@ -48,7 +48,7 @@ mod benchmarks { min_contribution, cap, end, - call.clone(), + Some(call.clone()), Some(target_address.clone()), ); @@ -66,7 +66,7 @@ mod benchmarks { funds_account: funds_account.clone(), raised: deposit, target_address: Some(target_address.clone()), - call: T::Preimages::bound(*call).unwrap(), + call: Some(T::Preimages::bound(*call).unwrap()), finalized: false, }) ); @@ -114,8 +114,8 @@ mod benchmarks { min_contribution, cap, end, - call.clone(), - Some(target_address.clone()), + Some(call), + Some(target_address), ); // setup contributor @@ -171,8 +171,8 @@ mod benchmarks { min_contribution, cap, end, - call.clone(), - Some(target_address.clone()), + Some(call), + Some(target_address), ); // create contribution @@ -237,7 +237,7 @@ mod benchmarks { min_contribution, cap, end, - call.clone(), + Some(call), Some(target_address.clone()), ); @@ -285,8 +285,8 @@ mod benchmarks { min_contribution, cap, end, - call, - Some(target_address.clone()), + Some(call), + Some(target_address), ); let crowdloan_id: CrowdloanId = 0; @@ -349,8 +349,8 @@ mod benchmarks { min_contribution, cap, end, - call, - Some(target_address.clone()), + Some(call), + Some(target_address), ); // run to the end of the contribution period @@ -386,7 +386,7 @@ mod benchmarks { min_contribution, cap, end, - call, + Some(call), None, ); @@ -433,7 +433,7 @@ mod benchmarks { min_contribution, cap, end, - call, + Some(call), None, ); @@ -472,7 +472,7 @@ mod benchmarks { min_contribution, cap, end, - call, + Some(call), None, ); diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index b09fab9c56..b88fe31596 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -46,7 +46,7 @@ pub type BoundedCallOf = Bounded<::RuntimeCall, ::Hashing>; /// A struct containing the information about a crowdloan. -#[freeze_struct("2fad4924268058e7")] +#[freeze_struct("6b86ccf70fc1b8f1")] #[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CrowdloanInfo { /// The creator of the crowdloan. @@ -67,8 +67,8 @@ pub struct CrowdloanInfo { /// provided, it means the funds will be transferred from on chain logic /// inside the provided call to dispatch. pub target_address: Option, - /// The call to dispatch when the crowdloan is finalized. - pub call: Call, + /// The optional call to dispatch when the crowdloan is finalized. + pub call: Option, /// Whether the crowdloan has been finalized. pub finalized: bool, } @@ -274,10 +274,10 @@ pub mod pallet { /// - `target_address`: The address to transfer the raised funds to if provided. #[pallet::call_index(0)] #[pallet::weight({ - let di = call.get_dispatch_info(); - let inner_call_weight = match di.pays_fee { - Pays::Yes => di.weight, - Pays::No => Weight::zero(), + let di = call.as_ref().map(|c| c.get_dispatch_info()); + let inner_call_weight = match di { + Some(di) => di.weight, + None => Weight::zero(), }; let base_weight = T::WeightInfo::create(); (base_weight.saturating_add(inner_call_weight), Pays::Yes) @@ -288,7 +288,7 @@ pub mod pallet { #[pallet::compact] min_contribution: BalanceOf, #[pallet::compact] cap: BalanceOf, #[pallet::compact] end: BlockNumberFor, - call: Box<::RuntimeCall>, + call: Option::RuntimeCall>>, target_address: Option, ) -> DispatchResult { let creator = ensure_signed(origin)?; @@ -322,6 +322,13 @@ pub mod pallet { let funds_account = Self::funds_account(crowdloan_id); frame_system::Pallet::::inc_providers(&funds_account); + // If the call is provided, bound it and store it in the preimage storage + let call = if let Some(call) = call { + Some(T::Preimages::bound(*call)?) + } else { + None + }; + let crowdloan = CrowdloanInfo { creator: creator.clone(), deposit, @@ -331,7 +338,7 @@ pub mod pallet { funds_account, raised: deposit, target_address, - call: T::Preimages::bound(*call)?, + call, finalized: false, }; Crowdloans::::insert(crowdloan_id, &crowdloan); @@ -526,28 +533,32 @@ pub mod pallet { )?; } - // Set the current crowdloan id so the dispatched call - // can access it temporarily - CurrentCrowdloanId::::put(crowdloan_id); - - // Retrieve the call from the preimage storage - let call = match T::Preimages::peek(&crowdloan.call) { - Ok((call, _)) => call, - Err(_) => { - // If the call is not found, we drop it from the preimage storage - // because it's not needed anymore - T::Preimages::drop(&crowdloan.call); - return Err(Error::::CallUnavailable)?; - } - }; - - // Dispatch the call with creator origin - call.dispatch(frame_system::RawOrigin::Signed(who).into()) - .map(|_| ()) - .map_err(|e| e.error)?; - - // Clear the current crowdloan id - CurrentCrowdloanId::::kill(); + // If the call is provided, dispatch it. + if let Some(ref call) = crowdloan.call { + // Set the current crowdloan id so the dispatched call + // can access it temporarily + CurrentCrowdloanId::::put(crowdloan_id); + + // Retrieve the call from the preimage storage + let stored_call = match T::Preimages::peek(call) { + Ok((call, _)) => call, + Err(_) => { + // If the call is not found, we drop it from the preimage storage + // because it's not needed anymore + T::Preimages::drop(call); + return Err(Error::::CallUnavailable)?; + } + }; + + // Dispatch the call with creator origin + stored_call + .dispatch(frame_system::RawOrigin::Signed(who).into()) + .map(|_| ()) + .map_err(|e| e.error)?; + + // Clear the current crowdloan id + CurrentCrowdloanId::::kill(); + } crowdloan.finalized = true; Crowdloans::::insert(crowdloan_id, &crowdloan); @@ -650,6 +661,11 @@ pub mod pallet { // there is no contributions or every contribution has been refunded ensure!(crowdloan.raised == 0, Error::::NotReadyToDissolve); + // Clear the call from the preimage storage + if let Some(call) = crowdloan.call { + T::Preimages::drop(&call); + } + // Remove the crowdloan let _ = frame_system::Pallet::::dec_providers(&crowdloan.funds_account).defensive(); Crowdloans::::remove(crowdloan_id); diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index 1cee6c9bef..59bfea6b83 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -25,7 +25,7 @@ fn test_create_succeeds() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -44,7 +44,7 @@ fn test_create_succeeds() { funds_account, raised: deposit, target_address: None, - call, + call: Some(call), finalized: false, }) ); @@ -97,7 +97,7 @@ fn test_create_fails_if_bad_origin() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), DispatchError::BadOrigin @@ -110,7 +110,7 @@ fn test_create_fails_if_bad_origin() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), DispatchError::BadOrigin @@ -136,7 +136,7 @@ fn test_create_fails_if_deposit_is_too_low() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), pallet_crowdloan::Error::::DepositTooLow @@ -162,7 +162,7 @@ fn test_create_fails_if_cap_is_not_greater_than_deposit() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), pallet_crowdloan::Error::::CapTooLow @@ -188,7 +188,7 @@ fn test_create_fails_if_min_contribution_is_too_low() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), pallet_crowdloan::Error::::MinimumContributionTooLow @@ -217,7 +217,7 @@ fn test_create_fails_if_end_is_in_the_past() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), pallet_crowdloan::Error::::CannotEndInPast @@ -243,7 +243,7 @@ fn test_create_fails_if_block_duration_is_too_short() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), pallet_crowdloan::Error::::BlockDurationTooShort @@ -269,7 +269,7 @@ fn test_create_fails_if_block_duration_is_too_long() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), pallet_crowdloan::Error::::BlockDurationTooLong @@ -295,7 +295,7 @@ fn test_create_fails_if_creator_has_insufficient_balance() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None ), pallet_crowdloan::Error::::InsufficientBalance @@ -323,7 +323,7 @@ fn test_contribute_succeeds() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -433,7 +433,7 @@ fn test_contribute_succeeds_if_contribution_will_make_the_raised_amount_exceed_t min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -554,7 +554,7 @@ fn test_contribute_fails_if_contribution_period_ended() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -592,7 +592,7 @@ fn test_contribute_fails_if_cap_has_been_raised() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -638,7 +638,7 @@ fn test_contribute_fails_if_contribution_is_below_minimum_contribution() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -675,7 +675,7 @@ fn test_contribute_fails_if_contributor_has_insufficient_balance() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -714,7 +714,7 @@ fn test_withdraw_succeeds() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -796,7 +796,7 @@ fn test_withdraw_succeeds_for_another_contributor() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -896,7 +896,7 @@ fn test_withdraw_fails_if_no_contribution_exists() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -942,6 +942,11 @@ fn test_finalize_succeeds() { let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; + let call = Box::new(RuntimeCall::TestPallet( + pallet_test::Call::::transfer_funds { + dest: U256::from(42), + }, + )); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), @@ -949,11 +954,7 @@ fn test_finalize_succeeds() { min_contribution, cap, end, - Box::new(RuntimeCall::TestPallet( - pallet_test::Call::::transfer_funds { - dest: U256::from(42), - } - )), + Some(call), None )); @@ -1019,6 +1020,9 @@ fn test_finalize_succeeds_with_target_address() { let cap: BalanceOf = 100; let end: BlockNumberFor = 50; let target_address: AccountOf = U256::from(42); + let call = Box::new(RuntimeCall::TestPallet( + pallet_test::Call::::set_passed_crowdloan_id {}, + )); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), @@ -1026,9 +1030,7 @@ fn test_finalize_succeeds_with_target_address() { min_contribution, cap, end, - Box::new(RuntimeCall::TestPallet( - pallet_test::Call::::set_passed_crowdloan_id {} - )), + Some(call), Some(target_address), )); @@ -1135,7 +1137,7 @@ fn test_finalize_fails_if_not_creator_origin() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None )); @@ -1182,7 +1184,7 @@ fn test_finalize_fails_if_crowdloan_has_not_ended() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1230,7 +1232,7 @@ fn test_finalize_fails_if_crowdloan_cap_is_not_raised() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1278,7 +1280,7 @@ fn test_finalize_fails_if_crowdloan_has_already_been_finalized() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1322,6 +1324,9 @@ fn test_finalize_fails_if_call_fails() { let min_contribution: BalanceOf = 10; let cap: BalanceOf = 100; let end: BlockNumberFor = 50; + let call = Box::new(RuntimeCall::TestPallet( + pallet_test::Call::::failing_extrinsic {}, + )); assert_ok!(Crowdloan::create( RuntimeOrigin::signed(creator), @@ -1329,9 +1334,7 @@ fn test_finalize_fails_if_call_fails() { min_contribution, cap, end, - Box::new(RuntimeCall::TestPallet( - pallet_test::Call::::failing_extrinsic {} - )), + Some(call), None, )); @@ -1382,7 +1385,7 @@ fn test_refund_succeeds() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1517,7 +1520,7 @@ fn test_refund_fails_if_crowdloan_has_not_ended() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1551,7 +1554,7 @@ fn test_dissolve_succeeds() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1629,7 +1632,7 @@ fn test_dissolve_fails_if_crowdloan_has_been_finalized() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1681,7 +1684,7 @@ fn test_dissolve_fails_if_origin_is_not_creator() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1717,7 +1720,7 @@ fn test_dissolve_fails_if_not_everyone_has_been_refunded() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1751,7 +1754,7 @@ fn test_update_min_contribution_succeeds() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1833,7 +1836,7 @@ fn test_update_min_contribution_fails_if_crowdloan_has_been_finalized() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1888,7 +1891,7 @@ fn test_update_min_contribution_fails_if_not_creator() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1924,7 +1927,7 @@ fn test_update_min_contribution_fails_if_new_min_contribution_is_too_low() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -1960,7 +1963,7 @@ fn test_update_end_succeeds() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2038,7 +2041,7 @@ fn test_update_end_fails_if_crowdloan_has_been_finalized() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2089,7 +2092,7 @@ fn test_update_end_fails_if_not_creator() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2122,7 +2125,7 @@ fn test_update_end_fails_if_new_end_is_in_past() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2154,7 +2157,7 @@ fn test_update_end_fails_if_block_duration_is_too_short() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2189,7 +2192,7 @@ fn test_update_end_fails_if_block_duration_is_too_long() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2221,7 +2224,7 @@ fn test_update_cap_succeeds() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2298,7 +2301,7 @@ fn test_update_cap_fails_if_crowdloan_has_been_finalized() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2349,7 +2352,7 @@ fn test_update_cap_fails_if_not_creator() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); @@ -2380,7 +2383,7 @@ fn test_update_cap_fails_if_new_cap_is_too_low() { min_contribution, cap, end, - noop_call(), + Some(noop_call()), None, )); From c34f35f080bbf21f871fae97002e1a81777e30e3 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 22 Apr 2025 14:22:20 +0200 Subject: [PATCH 64/70] rerun benchmarking --- pallets/crowdloan/src/weights.rs | 182 +++++++++++++++---------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/pallets/crowdloan/src/weights.rs b/pallets/crowdloan/src/weights.rs index 67f1c76b21..988f7a4efa 100644 --- a/pallets/crowdloan/src/weights.rs +++ b/pallets/crowdloan/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for `pallet_crowdloan` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 -//! DATE: 2025-04-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-04-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `Ubuntu-2404-noble-amd64-base`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: `1024` @@ -52,48 +52,48 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Crowdloan::Contributions` (r:0 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 40_335_000 picoseconds. - Weight::from_parts(41_647_000, 6148) + // Minimum execution time: 40_556_000 picoseconds. + Weight::from_parts(41_318_000, 6148) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `475` + // Measured: `476` // Estimated: `6148` - // Minimum execution time: 43_691_000 picoseconds. - Weight::from_parts(44_332_000, 6148) + // Minimum execution time: 42_900_000 picoseconds. + Weight::from_parts(43_682_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `436` // Estimated: `6148` - // Minimum execution time: 40_235_000 picoseconds. - Weight::from_parts(41_117_000, 6148) + // Minimum execution time: 41_037_000 picoseconds. + Weight::from_parts(41_968_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -102,28 +102,28 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: - // Measured: `375` + // Measured: `376` // Estimated: `6148` - // Minimum execution time: 40_636_000 picoseconds. - Weight::from_parts(41_497_000, 6148) + // Minimum execution time: 41_567_000 picoseconds. + Weight::from_parts(42_088_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) - /// Storage: `Crowdloan::Contributions` (r:6 w:5) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::Contributions` (r:51 w:50) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:6 w:6) + /// Storage: `System::Account` (r:51 w:51) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) - /// The range of component `k` is `[3, 5]`. + /// The range of component `k` is `[3, 50]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `460 + k * (45 ±0)` - // Estimated: `3742 + k * (2579 ±0)` - // Minimum execution time: 96_831_000 picoseconds. - Weight::from_parts(23_219_468, 3742) - // Standard Error: 128_696 - .saturating_add(Weight::from_parts(26_075_135, 0).saturating_mul(k.into())) + // Measured: `440 + k * (48 ±0)` + // Estimated: `3743 + k * (2579 ±0)` + // Minimum execution time: 97_612_000 picoseconds. + Weight::from_parts(36_327_787, 3743) + // Standard Error: 81_635 + .saturating_add(Weight::from_parts(25_989_645, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -131,48 +131,48 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn dissolve() -> Weight { // Proof Size summary in bytes: - // Measured: `320` - // Estimated: `3742` - // Minimum execution time: 11_551_000 picoseconds. - Weight::from_parts(12_092_000, 3742) + // Measured: `321` + // Estimated: `3743` + // Minimum execution time: 11_832_000 picoseconds. + Weight::from_parts(12_293_000, 3743) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) fn update_min_contribution() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `3742` - // Minimum execution time: 8_667_000 picoseconds. - Weight::from_parts(8_957_000, 3742) + // Measured: `224` + // Estimated: `3743` + // Minimum execution time: 8_776_000 picoseconds. + Weight::from_parts(9_057_000, 3743) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) fn update_end() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `3742` - // Minimum execution time: 8_776_000 picoseconds. - Weight::from_parts(9_067_000, 3742) + // Measured: `224` + // Estimated: `3743` + // Minimum execution time: 9_067_000 picoseconds. + Weight::from_parts(9_368_000, 3743) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) fn update_cap() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `3742` - // Minimum execution time: 8_546_000 picoseconds. - Weight::from_parts(8_927_000, 3742) + // Measured: `224` + // Estimated: `3743` + // Minimum execution time: 8_636_000 picoseconds. + Weight::from_parts(9_027_000, 3743) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -187,48 +187,48 @@ impl WeightInfo for () { /// Storage: `Crowdloan::Contributions` (r:0 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Crowdloans` (r:0 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: // Measured: `156` // Estimated: `6148` - // Minimum execution time: 40_335_000 picoseconds. - Weight::from_parts(41_647_000, 6148) + // Minimum execution time: 40_556_000 picoseconds. + Weight::from_parts(41_318_000, 6148) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn contribute() -> Weight { // Proof Size summary in bytes: - // Measured: `475` + // Measured: `476` // Estimated: `6148` - // Minimum execution time: 43_691_000 picoseconds. - Weight::from_parts(44_332_000, 6148) + // Minimum execution time: 42_900_000 picoseconds. + Weight::from_parts(43_682_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) /// Storage: `Crowdloan::Contributions` (r:1 w:1) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `436` // Estimated: `6148` - // Minimum execution time: 40_235_000 picoseconds. - Weight::from_parts(41_117_000, 6148) + // Minimum execution time: 41_037_000 picoseconds. + Weight::from_parts(41_968_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -237,28 +237,28 @@ impl WeightInfo for () { /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: - // Measured: `375` + // Measured: `376` // Estimated: `6148` - // Minimum execution time: 40_636_000 picoseconds. - Weight::from_parts(41_497_000, 6148) + // Minimum execution time: 41_567_000 picoseconds. + Weight::from_parts(42_088_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) - /// Storage: `Crowdloan::Contributions` (r:6 w:5) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::Contributions` (r:51 w:50) /// Proof: `Crowdloan::Contributions` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:6 w:6) + /// Storage: `System::Account` (r:51 w:51) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) - /// The range of component `k` is `[3, 5]`. + /// The range of component `k` is `[3, 50]`. fn refund(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `460 + k * (45 ±0)` - // Estimated: `3742 + k * (2579 ±0)` - // Minimum execution time: 96_831_000 picoseconds. - Weight::from_parts(23_219_468, 3742) - // Standard Error: 128_696 - .saturating_add(Weight::from_parts(26_075_135, 0).saturating_mul(k.into())) + // Measured: `440 + k * (48 ±0)` + // Estimated: `3743 + k * (2579 ±0)` + // Minimum execution time: 97_612_000 picoseconds. + Weight::from_parts(36_327_787, 3743) + // Standard Error: 81_635 + .saturating_add(Weight::from_parts(25_989_645, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -266,48 +266,48 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 2579).saturating_mul(k.into())) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) fn dissolve() -> Weight { // Proof Size summary in bytes: - // Measured: `320` - // Estimated: `3742` - // Minimum execution time: 11_551_000 picoseconds. - Weight::from_parts(12_092_000, 3742) + // Measured: `321` + // Estimated: `3743` + // Minimum execution time: 11_832_000 picoseconds. + Weight::from_parts(12_293_000, 3743) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) fn update_min_contribution() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `3742` - // Minimum execution time: 8_667_000 picoseconds. - Weight::from_parts(8_957_000, 3742) + // Measured: `224` + // Estimated: `3743` + // Minimum execution time: 8_776_000 picoseconds. + Weight::from_parts(9_057_000, 3743) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) fn update_end() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `3742` - // Minimum execution time: 8_776_000 picoseconds. - Weight::from_parts(9_067_000, 3742) + // Measured: `224` + // Estimated: `3743` + // Minimum execution time: 9_067_000 picoseconds. + Weight::from_parts(9_368_000, 3743) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) - /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(277), added: 2752, mode: `MaxEncodedLen`) + /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(278), added: 2753, mode: `MaxEncodedLen`) fn update_cap() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `3742` - // Minimum execution time: 8_546_000 picoseconds. - Weight::from_parts(8_927_000, 3742) + // Measured: `224` + // Estimated: `3743` + // Minimum execution time: 8_636_000 picoseconds. + Weight::from_parts(9_027_000, 3743) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } From 72be7c5a97d35cf6ea1aacd7257428e660881aa4 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 22 Apr 2025 17:28:59 +0200 Subject: [PATCH 65/70] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3c6b93f91a..e98a2d4bf3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -208,7 +208,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 262, + spec_version: 263, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 9e98765112b8c7dcfcfc7913791b46d33c14b8e9 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 23 Apr 2025 19:09:29 +0200 Subject: [PATCH 66/70] fix readme --- pallets/crowdloan/README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pallets/crowdloan/README.md b/pallets/crowdloan/README.md index 459d832533..f0b084b9ce 100644 --- a/pallets/crowdloan/README.md +++ b/pallets/crowdloan/README.md @@ -1,21 +1,19 @@ -# Crowdloan Pallet -A pallet allowing to create and manage generic crowdloans around a transfer of funds and a arbitrary call. +# Crowdloan Pallet -A user of this pallet can create a crowdloan by providing a deposit, a cap, an end block, a optionnal target address and a call. +A pallet that enables the creation and management of generic crowdloans for transferring funds and executing an arbitrary call. -Users will be able to contribute to the crowdloan by providing funds to the crowdloan they chose to contribute to. +Users of this pallet can create a crowdloan by providing a deposit, a cap, an end block, an optional target address and an optional call. -Once the crowdloan is finalized, the funds will be transferred to the target address if provided or the end user is expected to transfer them manually on chain if the call is a pallet extrinsic. The call will be dispatched with the current crowdloan id as a temporary storage item. +Users can contribute to a crowdloan by providing funds to the crowdloan they choose to support. -In case the crowdloan fails to raise the cap, the initial deposit will be returned to the creator and contributions will be returned to the contributors. +Once the crowdloan is finalized, the funds will be transferred to the target address if provided; otherwise, the end user is expected to transfer them manually on-chain if the call is a pallet extrinsic. The call will be dispatched with the current crowdloan ID stored as a temporary item. + +If the crowdloan fails to reach the cap, the initial deposit will be returned to the creator, and contributions will be refunded to the contributors. ## Overview ## Interface -### Dispatchable Functions - -[`Call`]: ./enum.Call.html -[`Config`]: ./trait.Config.html +## Dispatchable Functions License: Apache-2.0 From c19866d3de32caa1197d8a78880a63fe8f90e82b Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Apr 2025 10:27:17 +0200 Subject: [PATCH 67/70] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e98a2d4bf3..d13adc1e78 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -208,7 +208,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 263, + spec_version: 264, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From fc7d72e721049d85d16ee47904e032738a50eecb Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Apr 2025 11:49:20 +0200 Subject: [PATCH 68/70] expose CrowdloanInfoOf --- pallets/crowdloan/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index b88fe31596..bd3f0c4c9b 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -37,9 +37,9 @@ mod mock; mod tests; pub mod weights; -pub(crate) type CurrencyOf = ::Currency; +pub type CurrencyOf = ::Currency; -pub(crate) type BalanceOf = +pub type BalanceOf = as fungible::Inspect<::AccountId>>::Balance; pub type BoundedCallOf = From a247b675cbf6ceedbc72654a6f52876630eec02c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Apr 2025 12:00:28 +0200 Subject: [PATCH 69/70] expose CrowdloanInfoOf fix --- pallets/crowdloan/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index bd3f0c4c9b..2b977877e8 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -73,7 +73,7 @@ pub struct CrowdloanInfo { pub finalized: bool, } -type CrowdloanInfoOf = CrowdloanInfo< +pub type CrowdloanInfoOf = CrowdloanInfo< ::AccountId, BalanceOf, BlockNumberFor, From 6e2631dd523bf02c09d9c388dbb438a73432f5cf Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Apr 2025 13:24:06 +0200 Subject: [PATCH 70/70] expose CrowdloanId --- pallets/crowdloan/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 2b977877e8..5413bd689b 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -30,7 +30,7 @@ use weights::WeightInfo; pub use pallet::*; use subtensor_macros::freeze_struct; -type CrowdloanId = u32; +pub type CrowdloanId = u32; mod benchmarking; mod mock;