From e90df684b13c2fde813ddabc91a684fa0fef10b7 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Mon, 7 Aug 2023 11:52:52 +0200 Subject: [PATCH 01/14] feat(229): new vest method on common trait --- pallets/linear-release/src/impls.rs | 4 ++ pallets/linear-release/src/tests.rs | 57 +++++++++++++++++++++++++++-- traits/src/lib.rs | 6 +++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/pallets/linear-release/src/impls.rs b/pallets/linear-release/src/impls.rs index 4d1894986..7272335c8 100644 --- a/pallets/linear-release/src/impls.rs +++ b/pallets/linear-release/src/impls.rs @@ -357,4 +357,8 @@ impl ReleaseSchedule, ReasonOf> for Pallet { Self::write_release(who, locked_now, reason)?; Ok(()) } + + fn vest(who: AccountIdOf, reason: ReasonOf) -> DispatchResult { + Self::do_vest(who, reason) + } } diff --git a/pallets/linear-release/src/tests.rs b/pallets/linear-release/src/tests.rs index 409de08f3..675f8358e 100644 --- a/pallets/linear-release/src/tests.rs +++ b/pallets/linear-release/src/tests.rs @@ -1117,7 +1117,6 @@ fn vested_transfer_less_than_existential_deposit_fails() { }); } -// TODO #[test] fn set_release_schedule() { ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { @@ -1177,7 +1176,6 @@ fn set_release_schedule() { }); } -// TODO #[test] fn cannot_release_different_reason() { ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { @@ -1221,7 +1219,6 @@ fn cannot_release_different_reason() { }); } -// TODO #[test] fn multile_holds_release_schedule() { ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { @@ -1476,3 +1473,57 @@ fn manual_vest_all_different_reason() { assert_eq!(user_3_free_balance, 30 * ED); }); } + +#[test] +fn check_external_release_behavior() { + const ED: u64 = 10u64; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + assert_eq!(System::block_number(), 1); + // Initial Status + let user_3_initial_free_balance = Balances::free_balance(&3); + assert_eq!(user_3_initial_free_balance, 30 * ED); // 7680 ED + let user_3_reserved_balance = Balances::reserved_balance(&3); + assert_eq!(user_3_reserved_balance, 0); + let user_3_on_hold_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); + assert_eq!(user_3_on_hold_balance, 0); + + assert_ok!(Balances::hold(&LockType::Participation(0), &3, 4 * ED)); + let user_3_on_hold_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); + let user_3_initial_free_balance = Balances::free_balance(&3); + assert_eq!(user_3_initial_free_balance, 26 * ED); // 7680 ED + assert_eq!(user_3_on_hold_balance, 4 * ED); + let user_3_reserved_balance = Balances::reserved_balance(&3); + assert_eq!(user_3_reserved_balance, 4 * ED); + // assert_eq!(Vesting::vesting_balance(&3, LockType::Participation(0)), None); + + // Set release schedule to release the locked amount, starting from now, one ED per block. + assert_ok!(Vesting::set_release_schedule( + &3, + user_3_on_hold_balance, + ED, + 0, + LockType::Participation(0) + )); + + let user_3_on_hold_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); + assert_eq!(user_3_on_hold_balance, 4 * ED); + + // Release amount for block 0 to 3 + System::set_block_number(3); + assert_eq!(System::block_number(), 3); + assert_ok!(Vesting::vest(Some(3).into(), LockType::Participation(0))); + let free_balance = Balances::free_balance(&3); + let held_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); + assert_eq!(free_balance, 29 * ED); + assert_eq!(held_balance, 0 * ED); + + // Try releasing the remaining 2 ED, since the first release one was not available + System::set_block_number(4); + assert_eq!(System::block_number(), 4); + assert_ok!(Vesting::vest(Some(3).into(), LockType::Participation(0))); + let free_balance = Balances::free_balance(&3); + let held_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); + assert_eq!(free_balance, 30 * ED); + assert_eq!(held_balance, 0 * ED); + }); +} diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 7d998747c..46f637fd4 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -36,6 +36,12 @@ pub trait ReleaseSchedule { reason: Reason, ) -> Option<>::Balance>; + /// Release the vested amount of the given account. + fn vest( + who: AccountId, + reason: Reason, + ) -> DispatchResult; + /// Adds a release schedule to a given account. /// /// If the account has `MaxVestingSchedules`, an Error is returned and nothing From ec709bfb780cffd8f3f42f6d267d98a6ad6c2755 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Mon, 7 Aug 2023 14:49:25 +0200 Subject: [PATCH 02/14] feat(229): change vest storage for later use with vesting pallet --- Cargo.lock | 1 + pallets/funding/Cargo.toml | 1 + pallets/funding/src/functions.rs | 230 +++++++--------------------- pallets/funding/src/lib.rs | 28 +--- pallets/funding/src/tests.rs | 15 +- pallets/funding/src/types.rs | 47 ++---- pallets/linear-release/src/tests.rs | 1 + traits/src/lib.rs | 1 + 8 files changed, 73 insertions(+), 251 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05699b526..41f027c6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5994,6 +5994,7 @@ dependencies = [ "pallet-balances", "pallet-insecure-randomness-collective-flip", "pallet-linear-release", + "parachains-common", "parity-scale-codec", "polimec-traits", "scale-info", diff --git a/pallets/funding/Cargo.toml b/pallets/funding/Cargo.toml index 0780b4562..3d9a57015 100644 --- a/pallets/funding/Cargo.toml +++ b/pallets/funding/Cargo.toml @@ -28,6 +28,7 @@ sp-std.workspace = true sp-runtime.workspace = true sp-arithmetic.workspace = true polimec-traits.workspace = true +parachains-common.workspace = true [dev-dependencies] sp-core.workspace = true diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 4af678c62..1c0da94e0 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -36,7 +36,7 @@ use frame_support::{ use sp_arithmetic::Perquintill; -use sp_arithmetic::traits::{CheckedSub, Zero}; +use sp_arithmetic::traits::{CheckedDiv, CheckedSub, Zero}; use sp_std::prelude::*; // Round transition functions @@ -879,6 +879,7 @@ impl Pallet { let ticket_size = ct_usd_price.checked_mul_int(ct_amount).ok_or(Error::::BadMath)?; let funding_asset_usd_price = T::PriceProvider::get_price(funding_asset.to_statemint_id()).ok_or(Error::::PriceNotFound)?; + let plmc_usd_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).ok_or(Error::::PriceNotFound)?; let multiplier = multiplier.unwrap_or_default(); // * Validity checks * @@ -896,11 +897,13 @@ impl Pallet { ensure!(funding_asset == project_metadata.participation_currencies, Error::::FundingAssetNotAccepted); // * Calculate new variables * - let (plmc_vesting_period, ct_vesting_period) = - Self::calculate_vesting_periods(bidder.clone(), multiplier, ct_amount, ct_usd_price) + let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price) + .map_err(|_| Error::::BadMath)?; + let plmc_vesting_info = + Self::calculate_vesting_info(bidder.clone(), multiplier, plmc_bond) .map_err(|_| Error::::BadMath)?; - let required_plmc_bond = plmc_vesting_period.amount; - let required_funding_asset_transfer = + + let funding_asset_amount_locked = funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); let asset_id = funding_asset.to_statemint_id(); @@ -914,12 +917,11 @@ impl Pallet { final_ct_amount: ct_amount, final_ct_usd_price: ct_usd_price, funding_asset, - funding_asset_amount_locked: required_funding_asset_transfer, + funding_asset_amount_locked, multiplier, - plmc_bond: required_plmc_bond, + plmc_bond, + plmc_vesting_info, funded: false, - plmc_vesting_period, - ct_vesting_period, when: now, funds_released: false, ct_minted: false, @@ -927,8 +929,8 @@ impl Pallet { // * Update storage * if existing_bids.len() < T::MaxBidsPerUser::get() as usize { - Self::try_plmc_participation_lock(&bidder, project_id, required_plmc_bond)?; - Self::try_funding_asset_hold(&bidder, project_id, required_funding_asset_transfer, asset_id)?; + Self::try_plmc_participation_lock(&bidder, project_id, plmc_bond)?; + Self::try_funding_asset_hold(&bidder, project_id, funding_asset_amount_locked, asset_id)?; } else { let lowest_bid = existing_bids.iter().min_by_key(|bid| bid.plmc_bond).ok_or(Error::::ImpossibleState)?.clone(); @@ -950,8 +952,8 @@ impl Pallet { )?; Bids::::remove((project_id, lowest_bid.bidder, lowest_bid.id)); - Self::try_plmc_participation_lock(&bidder, project_id, required_plmc_bond)?; - Self::try_funding_asset_hold(&bidder, project_id, required_funding_asset_transfer, asset_id)?; + Self::try_plmc_participation_lock(&bidder, project_id, plmc_bond)?; + Self::try_funding_asset_hold(&bidder, project_id, funding_asset_amount_locked, asset_id)?; } Bids::::insert((project_id, bidder, bid_id), new_bid); @@ -992,6 +994,7 @@ impl Pallet { Contributions::::iter_prefix_values((project_id, contributor.clone())).collect::>(); let ct_usd_price = project_details.weighted_average_price.ok_or(Error::::AuctionNotStarted)?; + let plmc_usd_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).ok_or(Error::::PriceNotFound)?; let mut ticket_size = ct_usd_price.checked_mul_int(token_amount).ok_or(Error::::BadMath)?; let funding_asset_usd_price = T::PriceProvider::get_price(asset.to_statemint_id()).ok_or(Error::::PriceNotFound)?; @@ -1024,11 +1027,10 @@ impl Pallet { ticket_size = ct_usd_price.checked_mul_int(remaining_amount).ok_or(Error::::BadMath)?; remaining_amount }; - let (plmc_vesting_period, ct_vesting_period) = - Self::calculate_vesting_periods(contributor.clone(), multiplier.clone(), buyable_tokens, ct_usd_price) - .map_err(|_| Error::::BadMath)?; - let required_plmc_bond = plmc_vesting_period.amount; - let required_funding_asset_transfer = + let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price)?; + let plmc_vesting_info = + Self::calculate_vesting_info(contributor.clone(), multiplier.clone(), plmc_bond)?; + let funding_asset_amount = funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); let asset_id = asset.to_statemint_id(); let remaining_cts_after_purchase = project_details.remaining_contribution_tokens.saturating_sub(buyable_tokens); @@ -1037,13 +1039,12 @@ impl Pallet { id: contribution_id, project_id, contributor: contributor.clone(), - ct_amount: ct_vesting_period.amount, + ct_amount: token_amount, usd_contribution_amount: ticket_size, funding_asset: asset, - funding_asset_amount: required_funding_asset_transfer, - plmc_bond: required_plmc_bond, - plmc_vesting_period, - ct_vesting_period, + funding_asset_amount, + plmc_bond, + plmc_vesting_info, funds_released: false, ct_minted: false, }; @@ -1051,8 +1052,8 @@ impl Pallet { // * Update storage * // Try adding the new contribution to the system if existing_contributions.len() < T::MaxContributionsPerUser::get() as usize { - Self::try_plmc_participation_lock(&contributor, project_id, required_plmc_bond)?; - Self::try_funding_asset_hold(&contributor, project_id, required_funding_asset_transfer, asset_id)?; + Self::try_plmc_participation_lock(&contributor, project_id, plmc_bond)?; + Self::try_funding_asset_hold(&contributor, project_id, funding_asset_amount, asset_id)?; } else { let lowest_contribution = existing_contributions .iter() @@ -1076,8 +1077,8 @@ impl Pallet { )?; Contributions::::remove((project_id, lowest_contribution.contributor.clone(), lowest_contribution.id)); - Self::try_plmc_participation_lock(&contributor, project_id, required_plmc_bond)?; - Self::try_funding_asset_hold(&contributor, project_id, required_funding_asset_transfer, asset_id)?; + Self::try_plmc_participation_lock(&contributor, project_id, plmc_bond)?; + Self::try_funding_asset_hold(&contributor, project_id, funding_asset_amount, asset_id)?; project_details.remaining_contribution_tokens = project_details.remaining_contribution_tokens.saturating_add(lowest_contribution.ct_amount); @@ -1126,64 +1127,7 @@ impl Pallet { Ok(()) } - /// Unbond some plmc from a successful bid, after a step in the vesting period has passed. - /// - /// # Arguments - /// * bid: The bid to unbond from - /// - /// # Storage access - /// * [`Bids`] - Check if its time to unbond some plmc based on the bid vesting period, and update the bid after unbonding. - /// * [`BiddingBonds`] - Update the bid with the new vesting period struct, reflecting this withdrawal - /// * [`T::NativeCurrency`] - Unreserve the unbonded amount - pub fn do_vested_plmc_bid_unbond_for( - releaser: AccountIdOf, - project_id: T::ProjectIdentifier, - bidder: AccountIdOf, - ) -> Result<(), DispatchError> { - // * Get variables * - let bids = Bids::::iter_prefix_values((project_id, bidder.clone())); - let now = >::block_number(); - for mut bid in bids { - let mut plmc_vesting = bid.plmc_vesting_period; - - // * Validity checks * - // check that it is not too early to withdraw the next amount - if plmc_vesting.next_withdrawal > now { - continue - } - - // * Calculate variables * - let mut unbond_amount: BalanceOf = 0u32.into(); - // update vesting period until the next withdrawal is in the future - while let Ok(amount) = plmc_vesting.calculate_next_withdrawal() { - unbond_amount = unbond_amount.saturating_add(amount); - if plmc_vesting.next_withdrawal > now { - break - } - } - bid.plmc_vesting_period = plmc_vesting; - - // * Update storage * - T::NativeCurrency::release( - &LockType::Participation(project_id), - &bid.bidder, - unbond_amount, - Precision::Exact, - )?; - Bids::::insert((project_id, bidder.clone(), bid.id), bid.clone()); - - // * Emit events * - Self::deposit_event(Event::::BondReleased { - project_id: bid.project_id, - amount: unbond_amount, - bonder: bid.bidder, - releaser: releaser.clone(), - }); - } - - Ok(()) - } pub fn do_bid_ct_mint_for( releaser: AccountIdOf, @@ -1255,70 +1199,6 @@ impl Pallet { Ok(()) } - /// Unbond some plmc from a contribution, after a step in the vesting period has passed. - /// - /// # Arguments - /// * bid: The bid to unbond from - /// - /// # Storage access - /// * [`Bids`] - Check if its time to unbond some plmc based on the bid vesting period, and update the bid after unbonding. - /// * [`BiddingBonds`] - Update the bid with the new vesting period struct, reflecting this withdrawal - /// * [`T::NativeCurrency`] - Unreserve the unbonded amount - pub fn do_vested_plmc_purchase_unbond_for( - releaser: AccountIdOf, - project_id: T::ProjectIdentifier, - claimer: AccountIdOf, - ) -> Result<(), DispatchError> { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let contributions = Contributions::::iter_prefix_values((project_id, &claimer)); - let now = >::block_number(); - - // * Validity checks * - ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::CannotClaimYet); - - for mut contribution in contributions { - let mut plmc_vesting = contribution.plmc_vesting_period; - let mut unbond_amount: BalanceOf = 0u32.into(); - - // * Validity checks * - // check that it is not too early to withdraw the next amount - if plmc_vesting.next_withdrawal > now { - continue - } - - // * Calculate variables * - // Update vesting period until the next withdrawal is in the future - while let Ok(amount) = plmc_vesting.calculate_next_withdrawal() { - unbond_amount = unbond_amount.saturating_add(amount); - if plmc_vesting.next_withdrawal > now { - break - } - } - contribution.plmc_vesting_period = plmc_vesting; - - // * Update storage * - // Unreserve the funds for the user - T::NativeCurrency::release( - &LockType::Participation(project_id), - &claimer, - unbond_amount, - Precision::Exact, - )?; - Contributions::::insert((project_id, &claimer, contribution.id), contribution.clone()); - - // * Emit events * - Self::deposit_event(Event::BondReleased { - project_id, - amount: unbond_amount, - bonder: claimer.clone(), - releaser: releaser.clone(), - }) - } - - Ok(()) - } - pub fn do_evaluation_unbond_for( releaser: AccountIdOf, project_id: T::ProjectIdentifier, @@ -1564,42 +1444,36 @@ impl Pallet { Ok(()) } + pub fn calculate_plmc_bond(ticket_size: BalanceOf, multiplier: MultiplierOf, plmc_price: PriceOf) -> Result, DispatchError> { + let usd_bond = + multiplier.calculate_bonding_requirement(ticket_size).map_err(|_| Error::::BadMath)?; + plmc_price + .reciprocal() + .ok_or(Error::::BadMath)? + .checked_mul_int(usd_bond) + .ok_or(Error::::BadMath.into()) + } + /// Based on the amount of tokens and price to buy, a desired multiplier, and the type of investor the caller is, /// calculate the amount and vesting periods of bonded PLMC and reward CT tokens. - pub fn calculate_vesting_periods( + pub fn calculate_vesting_info( _caller: AccountIdOf, - multiplier: MultiplierOf, - token_amount: BalanceOf, - token_price: T::Price, - ) -> Result<(Vesting>, Vesting>), DispatchError> { + _multiplier: MultiplierOf, + plmc_bonded_amount: BalanceOf, + ) -> Result>, DispatchError> { let plmc_start: T::BlockNumber = 0u32.into(); - let ct_start: T::BlockNumber = (T::MaxProjectsToUpdatePerBlock::get() * 7).into(); - // TODO: Calculate real vesting periods based on multiplier and caller type - let ticket_size = token_price.checked_mul_int(token_amount).ok_or(Error::::BadMath)?; - let usd_bonding_amount = - multiplier.calculate_bonding_requirement(ticket_size).map_err(|_| Error::::BadMath)?; - let plmc_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).ok_or(Error::::PLMCPriceNotAvailable)?; - let plmc_bonding_amount = plmc_price - .reciprocal() - .ok_or(Error::::BadMath)? - .checked_mul_int(usd_bonding_amount) - .ok_or(Error::::BadMath)?; - Ok(( - Vesting { - amount: plmc_bonding_amount, + + // TODO: lock_time should depend on `_multiplier` and `_caller` credential + let lock_time = 7 * parachains_common::DAYS; + let amount_per_block = plmc_bonded_amount.checked_div(&lock_time.into()).ok_or(Error::::BadMath)?; + + Ok( + VestingInfo { + amount_per_block, start: plmc_start, - end: plmc_start, - step: 0u32.into(), - next_withdrawal: 0u32.into(), - }, - Vesting { - amount: token_amount, - start: ct_start, - end: ct_start, - step: 0u32.into(), - next_withdrawal: 0u32.into(), + end: plmc_start.saturating_add(lock_time.into()), }, - )) + ) } /// Calculates the price (in USD) of contribution tokens for the Community and Remainder Rounds diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 496855513..43a17966a 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -231,7 +231,7 @@ pub type ProjectMetadataOf = pub type ProjectDetailsOf = ProjectDetails, BlockNumberOf, PriceOf, BalanceOf, EvaluationRoundInfoOf>; pub type EvaluationRoundInfoOf = EvaluationRoundInfo>; -pub type VestingOf = Vesting, BalanceOf>; +pub type VestingInfoOf = VestingInfo, BalanceOf>; pub type EvaluationInfoOf = EvaluationInfo, ProjectIdOf, AccountIdOf, BalanceOf, BlockNumberOf>; pub type BidInfoOf = BidInfo< @@ -241,12 +241,11 @@ pub type BidInfoOf = BidInfo< PriceOf, AccountIdOf, BlockNumberOf, - VestingOf, - VestingOf, MultiplierOf, + VestingInfoOf, >; pub type ContributionInfoOf = - ContributionInfo, ProjectIdOf, AccountIdOf, BalanceOf, VestingOf, VestingOf>; + ContributionInfo, ProjectIdOf, AccountIdOf, BalanceOf, VestingInfoOf>; pub type BondTypeOf = LockType>; const PLMC_STATEMINT_ID: u32 = 2069; @@ -857,27 +856,6 @@ pub mod pallet { let caller = ensure_signed(origin)?; Self::do_contribution_ct_mint_for(caller, project_id, contributor, contribution_id) } - - /// Unbond some plmc from a contribution, after a step in the vesting period has passed. - pub fn vested_plmc_bid_unbond_for( - origin: OriginFor, - project_id: T::ProjectIdentifier, - bidder: AccountIdOf, - ) -> DispatchResult { - let releaser = ensure_signed(origin)?; - - Self::do_vested_plmc_bid_unbond_for(releaser, project_id, bidder) - } - - /// Unbond some plmc from a contribution, after a step in the vesting period has passed. - pub fn vested_plmc_purchase_unbond_for( - origin: OriginFor, - project_id: T::ProjectIdentifier, - purchaser: AccountIdOf, - ) -> DispatchResult { - let releaser = ensure_signed(origin)?; - Self::do_vested_plmc_purchase_unbond_for(releaser, project_id, purchaser) - } } #[pallet::hooks] diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index df23756fa..d2ca2083d 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -99,7 +99,6 @@ pub struct BidInfoFilter< Multiplier, BlockNumber, PlmcVesting, - CTVesting, > { pub id: Option, pub project_id: Option, @@ -114,8 +113,7 @@ pub struct BidInfoFilter< pub multiplier: Option, pub plmc_bond: Option, pub funded: Option, - pub plmc_vesting_period: Option, - pub ct_vesting_period: Option, + pub plmc_vesting_info: Option, pub when: Option, pub funds_released: Option, } @@ -127,8 +125,7 @@ type BidInfoFilterOf = BidInfoFilter< ::AccountId, MultiplierOf, BlockNumberOf, - VestingOf, - VestingOf, + VestingInfoOf, >; impl Default for BidInfoFilterOf { fn default() -> Self { @@ -146,8 +143,7 @@ impl Default for BidInfoFilterOf { multiplier: None, plmc_bond: None, funded: None, - plmc_vesting_period: None, - ct_vesting_period: None, + plmc_vesting_info: None, when: None, funds_released: None, } @@ -196,10 +192,7 @@ impl BidInfoFilterOf { if self.funded.is_some() && self.funded.unwrap() != bid.funded { return false } - if self.plmc_vesting_period.is_some() && self.plmc_vesting_period.unwrap() != bid.plmc_vesting_period { - return false - } - if self.ct_vesting_period.is_some() && self.ct_vesting_period.unwrap() != bid.ct_vesting_period { + if self.plmc_vesting_info.is_some() && self.plmc_vesting_info.unwrap() != bid.plmc_vesting_info { return false } if self.when.is_some() && self.when.unwrap() != bid.when { diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 325e469bd..77a81d002 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -175,9 +175,8 @@ pub mod storage_types { Price: FixedPointNumber, AccountId, BlockNumber, - PlmcVesting, - CTVesting, Multiplier, + VestingInfo > { pub id: Id, pub project_id: ProjectId, @@ -192,9 +191,8 @@ pub mod storage_types { pub funding_asset_amount_locked: Balance, pub multiplier: Multiplier, pub plmc_bond: Balance, + pub plmc_vesting_info: VestingInfo, pub funded: bool, - pub plmc_vesting_period: PlmcVesting, - pub ct_vesting_period: CTVesting, pub when: BlockNumber, pub funds_released: bool, pub ct_minted: bool, @@ -207,10 +205,9 @@ pub mod storage_types { Price: FixedPointNumber, AccountId: Eq, BlockNumber: Eq + Ord, - PlmcVesting: Eq, - CTVesting: Eq, Multiplier: Eq, - > Ord for BidInfo + VestingInfo: Eq + > Ord for BidInfo { fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { match self.original_ct_usd_price.cmp(&other.original_ct_usd_price) { @@ -227,10 +224,9 @@ pub mod storage_types { Price: FixedPointNumber, AccountId: Eq, BlockNumber: Eq + Ord, - PlmcVesting: Eq, - CTVesting: Eq, Multiplier: Eq, - > PartialOrd for BidInfo + VestingInfo: Eq + > PartialOrd for BidInfo { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -238,7 +234,7 @@ pub mod storage_types { } #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] - pub struct ContributionInfo { + pub struct ContributionInfo { pub id: Id, pub project_id: ProjectId, pub contributor: AccountId, @@ -247,8 +243,7 @@ pub mod storage_types { pub funding_asset: AcceptedFundingAsset, pub funding_asset_amount: Balance, pub plmc_bond: Balance, - pub plmc_vesting_period: PLMCVesting, - pub ct_vesting_period: CTVesting, + pub plmc_vesting_info: VestingInfo, pub funds_released: bool, pub ct_minted: bool, } @@ -438,35 +433,13 @@ pub mod inner_types { } #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub struct Vesting { + pub struct VestingInfo { // Amount of tokens vested - pub amount: Balance, + pub amount_per_block: Balance, // number of blocks after project ends, when vesting starts pub start: BlockNumber, // number of blocks after project ends, when vesting ends pub end: BlockNumber, - // number of blocks between each withdrawal - pub step: BlockNumber, - // absolute block number of next block where withdrawal is possible - pub next_withdrawal: BlockNumber, - } - - impl< - BlockNumber: Saturating + Copy + CheckedDiv, - Balance: Saturating + CheckedDiv + Copy + From + Eq + sp_std::ops::SubAssign, - > Vesting - { - pub fn calculate_next_withdrawal(&mut self) -> Result { - if self.amount == 0u32.into() { - Err(()) - } else { - let next_withdrawal = self.next_withdrawal.saturating_add(self.step); - let withdraw_amount = self.amount; - self.next_withdrawal = next_withdrawal; - self.amount -= withdraw_amount; - Ok(withdraw_amount) - } - } } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] diff --git a/pallets/linear-release/src/tests.rs b/pallets/linear-release/src/tests.rs index 675f8358e..5df508c4e 100644 --- a/pallets/linear-release/src/tests.rs +++ b/pallets/linear-release/src/tests.rs @@ -1475,6 +1475,7 @@ fn manual_vest_all_different_reason() { } #[test] +#[ignore] fn check_external_release_behavior() { const ED: u64 = 10u64; ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 46f637fd4..350e0900d 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -36,6 +36,7 @@ pub trait ReleaseSchedule { reason: Reason, ) -> Option<>::Balance>; + /// Release the vested amount of the given account. fn vest( who: AccountId, From 2047230d7cd9d720a36fbfdac64ee97fe32e9fed Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Tue, 8 Aug 2023 09:56:23 +0200 Subject: [PATCH 03/14] feat(229): state machine impl on plmc vesting for bids --- integration-tests/src/lib.rs | 1 - pallets/funding/Cargo.toml | 1 + pallets/funding/src/functions.rs | 100 ++++++++++++++++++---------- pallets/funding/src/impls.rs | 80 +++++++++++++--------- pallets/funding/src/lib.rs | 15 ++++- pallets/funding/src/mock.rs | 2 +- pallets/funding/src/tests.rs | 12 +--- pallets/funding/src/types.rs | 26 +++++--- pallets/linear-release/src/tests.rs | 8 +-- runtimes/testnet/src/xcm_config.rs | 2 +- traits/src/lib.rs | 6 +- 11 files changed, 150 insertions(+), 103 deletions(-) diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index f2fd07b1a..b9ae7961e 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -16,7 +16,6 @@ #![cfg(test)] - use frame_support::{assert_ok, pallet_prelude::Weight, traits::GenesisBuild}; use parity_scale_codec::Encode; use polimec_parachain_runtime as polimec_runtime; diff --git a/pallets/funding/Cargo.toml b/pallets/funding/Cargo.toml index 3d9a57015..55d5a055c 100644 --- a/pallets/funding/Cargo.toml +++ b/pallets/funding/Cargo.toml @@ -51,6 +51,7 @@ std = [ "frame-system/std", "pallet-assets/std", "pallet-balances/std", + "parachains-common/std", "polimec-traits/std", "pallet-linear-release/std", "frame-benchmarking?/std", diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 1c0da94e0..71fd37ec4 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -33,11 +33,13 @@ use frame_support::{ Get, }, }; +use parachains_common::BlockNumber; use sp_arithmetic::Perquintill; use sp_arithmetic::traits::{CheckedDiv, CheckedSub, Zero}; use sp_std::prelude::*; +use polimec_traits::ReleaseSchedule; // Round transition functions impl Pallet { @@ -107,6 +109,7 @@ impl Pallet { total_bonded_plmc: Zero::zero(), evaluators_outcome: EvaluatorsOutcome::Unchanged, }, + funding_end_block: None }; let project_metadata = initial_metadata; @@ -650,6 +653,7 @@ impl Pallet { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; let token_information = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectNotFound)?.token_information; + let now = >::block_number(); // * Validity checks * ensure!( @@ -658,10 +662,13 @@ impl Pallet { Error::::NotAllowed ); - // * Update storage * - + // * Calculate new variables * project_details.cleanup = Cleaner::try_from(project_details.status.clone()).map_err(|_| Error::::NotAllowed)?; + project_details.funding_end_block = Some(now); + + // * Update storage * + ProjectsDetails::::insert(project_id, project_details.clone()); if project_details.status == ProjectStatus::FundingSuccessful { @@ -738,7 +745,7 @@ impl Pallet { ensure!(project_details.issuer == issuer, Error::::NotAllowed); ensure!(!project_details.is_frozen, Error::::Frozen); ensure!(!Images::::contains_key(project_metadata_hash), Error::::MetadataAlreadyExists); - + // * Calculate new variables * // * Update Storage * @@ -897,11 +904,10 @@ impl Pallet { ensure!(funding_asset == project_metadata.participation_currencies, Error::::FundingAssetNotAccepted); // * Calculate new variables * - let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price) - .map_err(|_| Error::::BadMath)?; + let plmc_bond = + Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price).map_err(|_| Error::::BadMath)?; let plmc_vesting_info = - Self::calculate_vesting_info(bidder.clone(), multiplier, plmc_bond) - .map_err(|_| Error::::BadMath)?; + Self::calculate_vesting_info(bidder.clone(), multiplier, plmc_bond).map_err(|_| Error::::BadMath)?; let funding_asset_amount_locked = funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); @@ -1018,7 +1024,7 @@ impl Pallet { ensure!(ticket_size <= maximum_ticket_size, Error::::ContributionTooHigh); }; ensure!(project_metadata.participation_currencies == asset, Error::::FundingAssetNotAccepted); - + // * Calculate variables * let buyable_tokens = if project_details.remaining_contribution_tokens > token_amount { token_amount @@ -1028,8 +1034,7 @@ impl Pallet { remaining_amount }; let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price)?; - let plmc_vesting_info = - Self::calculate_vesting_info(contributor.clone(), multiplier.clone(), plmc_bond)?; + let plmc_vesting_info = Self::calculate_vesting_info(contributor.clone(), multiplier.clone(), plmc_bond)?; let funding_asset_amount = funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); let asset_id = asset.to_statemint_id(); @@ -1127,8 +1132,6 @@ impl Pallet { Ok(()) } - - pub fn do_bid_ct_mint_for( releaser: AccountIdOf, project_id: T::ProjectIdentifier, @@ -1352,6 +1355,43 @@ impl Pallet { Ok(()) } + pub fn do_start_bid_vesting_schedule_for( + caller: AccountIdOf, + project_id: T::ProjectIdentifier, + bidder: AccountIdOf, + bid_id: StorageItemIdOf, + ) -> Result<(), DispatchError> { + // * Get variables * + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; + let mut bid = Bids::::get((project_id, bidder.clone(), bid_id)).ok_or(Error::::BidNotFound)?; + let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; + let vest_info = bid.plmc_vesting_info.clone(); + + // * Validity checks * + ensure!( + !bid.plmc_vesting_info.scheduled && project_details.status == ProjectStatus::FundingSuccessful, + Error::::NotAllowed + ); + + // * Calculate variables * + bid.plmc_vesting_info.scheduled = true; + + // * Update storage * + T::Vesting::add_release_schedule(&bidder, vest_info.total_amount, vest_info.amount_per_block, funding_end_block, LockType::Participation(project_id))?; + Bids::::insert((project_id, bidder.clone(), bid_id), bid); + + // * Emit events * + Self::deposit_event(Event::::BidPlmcVestingScheduled { + project_id, + bidder: bidder.clone(), + id: bid_id, + amount: vest_info.total_amount, + caller + }); + + Ok(()) + } + pub fn do_release_bid_funds_for( _caller: AccountIdOf, _project_id: T::ProjectIdentifier, @@ -1405,6 +1445,7 @@ impl Pallet { ) -> Result<(), DispatchError> { Ok(()) } + } // Helper functions @@ -1423,7 +1464,7 @@ impl Pallet { // Try to get the project into the earliest possible block to update. // There is a limit for how many projects can update each block, so we need to make sure we don't exceed that limit let mut block_number = block_number; - while ProjectsToUpdate::::try_append(block_number, store.clone()).is_err() { + while ProjectsToUpdate::::try_append(block_number.clone(), store.clone()).is_err() { // TODO: Should we end the loop if we iterated over too many blocks? block_number += 1u32.into(); } @@ -1444,14 +1485,13 @@ impl Pallet { Ok(()) } - pub fn calculate_plmc_bond(ticket_size: BalanceOf, multiplier: MultiplierOf, plmc_price: PriceOf) -> Result, DispatchError> { - let usd_bond = - multiplier.calculate_bonding_requirement(ticket_size).map_err(|_| Error::::BadMath)?; - plmc_price - .reciprocal() - .ok_or(Error::::BadMath)? - .checked_mul_int(usd_bond) - .ok_or(Error::::BadMath.into()) + pub fn calculate_plmc_bond( + ticket_size: BalanceOf, + multiplier: MultiplierOf, + plmc_price: PriceOf, + ) -> Result, DispatchError> { + let usd_bond = multiplier.calculate_bonding_requirement(ticket_size).map_err(|_| Error::::BadMath)?; + plmc_price.reciprocal().ok_or(Error::::BadMath)?.checked_mul_int(usd_bond).ok_or(Error::::BadMath.into()) } /// Based on the amount of tokens and price to buy, a desired multiplier, and the type of investor the caller is, @@ -1459,21 +1499,13 @@ impl Pallet { pub fn calculate_vesting_info( _caller: AccountIdOf, _multiplier: MultiplierOf, - plmc_bonded_amount: BalanceOf, + bonded_amount: BalanceOf, ) -> Result>, DispatchError> { - let plmc_start: T::BlockNumber = 0u32.into(); - // TODO: lock_time should depend on `_multiplier` and `_caller` credential - let lock_time = 7 * parachains_common::DAYS; - let amount_per_block = plmc_bonded_amount.checked_div(&lock_time.into()).ok_or(Error::::BadMath)?; - - Ok( - VestingInfo { - amount_per_block, - start: plmc_start, - end: plmc_start.saturating_add(lock_time.into()), - }, - ) + let duration: u32 = 7u32 * parachains_common::DAYS; + let amount_per_block = bonded_amount.checked_div(&duration.into()).ok_or(Error::::BadMath)?; + + Ok(VestingInfo { total_amount: bonded_amount, amount_per_block, duration: >::from(duration), scheduled: false }) } /// Calculates the price (in USD) of contribution tokens for the Community and Remainder Rounds diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index ea1a8ba25..0e639bbe8 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -50,36 +50,39 @@ impl DoRemainingOperation for CleanerState { }, CleanerState::EvaluationUnbonding(remaining, PhantomData) => if *remaining == 0 { - *self = - CleanerState::BidPLMCVesting(remaining_bids_without_plmc_vesting::(project_id), PhantomData); + *self = CleanerState::StartBidderVestingSchedule( + remaining_bids::(project_id), + PhantomData, + ); Ok(Weight::zero()) } else { let (consumed_weight, remaining_evaluations) = unbond_one_evaluation::(project_id); *self = CleanerState::EvaluationUnbonding(remaining_evaluations, PhantomData); Ok(consumed_weight) }, - CleanerState::BidPLMCVesting(remaining, PhantomData) => + CleanerState::StartBidderVestingSchedule(remaining, PhantomData) => if *remaining == 0 { - *self = CleanerState::BidCTMint(remaining_bids_without_ct_minted::(project_id), PhantomData); + *self = CleanerState::StartContributorVestingSchedule( + remaining_contributions::(project_id), + PhantomData, + ); Ok(Weight::zero()) } else { - let (consumed_weight, remaining_bids) = start_bid_plmc_vesting_schedule::(project_id); - *self = CleanerState::BidPLMCVesting(remaining_bids, PhantomData); + let (consumed_weight, remaining_evaluations) = start_one_bid_vesting_schedule::(project_id); + *self = CleanerState::StartBidderVestingSchedule(remaining_evaluations, PhantomData); Ok(consumed_weight) }, - CleanerState::BidCTMint(remaining, PhantomData) => + CleanerState::StartContributorVestingSchedule(remaining, PhantomData) => if *remaining == 0 { - *self = CleanerState::ContributionPLMCVesting( - remaining_contributions_without_plmc_vesting::(project_id), - PhantomData, - ); + *self = CleanerState::BidCTMint(remaining_bids_without_ct_minted::(project_id), PhantomData); Ok(Weight::zero()) } else { - let (consumed_weight, remaining_bids) = mint_ct_for_one_bid::(project_id); - *self = CleanerState::BidCTMint(remaining_bids, PhantomData); + let (consumed_weight, remaining_evaluations) = + start_one_contribution_vesting_schedule::(project_id); + *self = CleanerState::StartContributorVestingSchedule(remaining_evaluations, PhantomData); Ok(consumed_weight) }, - CleanerState::ContributionPLMCVesting(remaining, PhantomData) => + CleanerState::BidCTMint(remaining, PhantomData) => if *remaining == 0 { *self = CleanerState::ContributionCTMint( remaining_contributions_without_ct_minted::(project_id), @@ -87,9 +90,8 @@ impl DoRemainingOperation for CleanerState { ); Ok(Weight::zero()) } else { - let (consumed_weight, remaining_contributions) = - start_contribution_plmc_vesting_schedule::(project_id); - *self = CleanerState::ContributionPLMCVesting(remaining_contributions, PhantomData); + let (consumed_weight, remaining_bids) = mint_ct_for_one_bid::(project_id); + *self = CleanerState::BidCTMint(remaining_bids, PhantomData); Ok(consumed_weight) }, CleanerState::ContributionCTMint(remaining, PhantomData) => @@ -264,23 +266,11 @@ fn remaining_contributions(project_id: T::ProjectIdentifier) -> u64 { Contributions::::iter_prefix_values((project_id,)).count() as u64 } -fn remaining_bids_without_plmc_vesting(_project_id: T::ProjectIdentifier) -> u64 { - // TODO: current vesting implementation starts the schedule on bid creation. We should later on use pallet_vesting - // and add a check in the bid struct for initializing the vesting schedule - 0u64 -} - fn remaining_bids_without_ct_minted(project_id: T::ProjectIdentifier) -> u64 { let project_bids = Bids::::iter_prefix_values((project_id,)); project_bids.filter(|bid| !bid.ct_minted).count() as u64 } -fn remaining_contributions_without_plmc_vesting(_project_id: T::ProjectIdentifier) -> u64 { - // TODO: current vesting implementation starts the schedule on contribution creation. We should later on use pallet_vesting - // and add a check in the contribution struct for initializing the vesting schedule - 0u64 -} - fn remaining_contributions_without_ct_minted(project_id: T::ProjectIdentifier) -> u64 { let project_contributions = Contributions::::iter_prefix_values((project_id,)); project_contributions.filter(|contribution| !contribution.ct_minted).count() as u64 @@ -489,12 +479,36 @@ fn unbond_one_contribution(project_id: T::ProjectIdentifier) -> (Weig } } -fn start_bid_plmc_vesting_schedule(_project_id: T::ProjectIdentifier) -> (Weight, u64) { - // TODO: change when new vesting schedule is implemented - (Weight::zero(), 0u64) +fn start_one_bid_vesting_schedule(project_id: T::ProjectIdentifier) -> (Weight, u64) { + let project_bids = Bids::::iter_prefix_values((project_id,)); + let mut unscheduled_bids = project_bids.filter(|bid| !bid.plmc_vesting_info.scheduled); + + if let Some(mut bid) = unscheduled_bids.next() { + match Pallet::::do_start_bid_vesting_schedule_for(T::PalletId::get().into_account_truncating(), project_id, bid.bidder.clone(), bid.id) { + Ok(_) => { + bid.plmc_vesting_info.scheduled = true; + Bids::::insert((project_id, bid.bidder.clone(), bid.id), bid); + }, + Err(e) => { + Pallet::::deposit_event(Event::StartBidderVestingScheduleFailed { + project_id: bid.project_id, + bidder: bid.bidder.clone(), + id: bid.id, + error: e, + }); + }, + } + + (Weight::zero(), unscheduled_bids.count() as u64) + + } else { + (Weight::zero(), 0u64) + } + + } -fn start_contribution_plmc_vesting_schedule(_project_id: T::ProjectIdentifier) -> (Weight, u64) { +fn start_one_contribution_vesting_schedule(_project_id: T::ProjectIdentifier) -> (Weight, u64) { // TODO: change when new vesting schedule is implemented (Weight::zero(), 0u64) } diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 43a17966a..4e9ac2bc6 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -376,7 +376,7 @@ pub mod pallet { type EvaluationSuccessThreshold: Get; - type Vesting: polimec_traits::ReleaseSchedule, BondTypeOf>; + type Vesting: polimec_traits::ReleaseSchedule, BondTypeOf, Currency=Self::NativeCurrency, Moment=BlockNumberOf>; /// For now we expect 3 days until the project is automatically accepted. Timeline decided by MiCA regulations. type ManualAcceptanceDuration: Get; /// For now we expect 4 days from acceptance to settlement due to MiCA regulations. @@ -615,6 +615,19 @@ pub mod pallet { id: StorageItemIdOf, error: DispatchError, }, + StartBidderVestingScheduleFailed { + project_id: ProjectIdOf, + bidder: AccountIdOf, + id: StorageItemIdOf, + error: DispatchError, + }, + BidPlmcVestingScheduled { + project_id: ProjectIdOf, + bidder: AccountIdOf, + id: StorageItemIdOf, + amount: BalanceOf, + caller: AccountIdOf, + }, } #[pallet::error] diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 59c6d6719..1ebde34b4 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -270,7 +270,7 @@ impl pallet_funding::Config for TestRuntime { type Randomness = RandomnessCollectiveFlip; type RemainderFundingDuration = RemainderFundingDuration; type RuntimeEvent = RuntimeEvent; - type StorageItemId = u128; + type StorageItemId = u32; type StringLimit = ConstU32<64>; type SuccessToSettlementTime = SuccessToSettlementTime; type TreasuryAccount = TreasuryAccount; diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index d2ca2083d..5a0094533 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -90,16 +90,7 @@ impl TestContribution { pub type TestContributions = Vec; #[derive(Clone, PartialEq, Eq, Debug)] -pub struct BidInfoFilter< - Id, - ProjectId, - Balance: BalanceT, - Price, - AccountId, - Multiplier, - BlockNumber, - PlmcVesting, -> { +pub struct BidInfoFilter { pub id: Option, pub project_id: Option, pub bidder: Option, @@ -588,6 +579,7 @@ impl<'a> CreatedProject<'a> { total_bonded_plmc: Zero::zero(), evaluators_outcome: EvaluatorsOutcome::Unchanged, }, + funding_end_block: None, }; assert_eq!(metadata, expected_metadata); assert_eq!(details, expected_details); diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 77a81d002..9ee91119b 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -61,6 +61,12 @@ pub mod config_types { Participation(ProjectId), } + #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo, Ord, PartialOrd)] + pub enum ParticipationType { + Bid(StorageItemId), + Contribution(StorageItemId) + } + pub struct ConstPriceProvider(PhantomData<(AssetId, Price, Mapping)>); impl>> ProvideStatemintPrice for ConstPriceProvider @@ -138,6 +144,8 @@ pub mod storage_types { pub cleanup: Cleaner, /// Information about the total amount bonded, and the outcome in regards to reward/slash/nothing pub evaluation_round_info: EvaluationRoundInfo, + + pub funding_end_block: Option, } /// Tells on_initialize what to do with the project @@ -176,7 +184,7 @@ pub mod storage_types { AccountId, BlockNumber, Multiplier, - VestingInfo + VestingInfo, > { pub id: Id, pub project_id: ProjectId, @@ -206,7 +214,7 @@ pub mod storage_types { AccountId: Eq, BlockNumber: Eq + Ord, Multiplier: Eq, - VestingInfo: Eq + VestingInfo: Eq, > Ord for BidInfo { fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { @@ -225,7 +233,7 @@ pub mod storage_types { AccountId: Eq, BlockNumber: Eq + Ord, Multiplier: Eq, - VestingInfo: Eq + VestingInfo: Eq, > PartialOrd for BidInfo { fn partial_cmp(&self, other: &Self) -> Option { @@ -434,12 +442,10 @@ pub mod inner_types { #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct VestingInfo { - // Amount of tokens vested + pub total_amount: Balance, pub amount_per_block: Balance, - // number of blocks after project ends, when vesting starts - pub start: BlockNumber, - // number of blocks after project ends, when vesting ends - pub end: BlockNumber, + pub duration: BlockNumber, + pub scheduled: bool } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -478,10 +484,10 @@ pub mod inner_types { EvaluationUnbonding(u64, PhantomData), // Branch // A. Success only - BidPLMCVesting(u64, PhantomData), BidCTMint(u64, PhantomData), - ContributionPLMCVesting(u64, PhantomData), ContributionCTMint(u64, PhantomData), + StartBidderVestingSchedule(u64, PhantomData), + StartContributorVestingSchedule(u64, PhantomData), BidFundingPayout(u64, PhantomData), ContributionFundingPayout(u64, PhantomData), // B. Failure only diff --git a/pallets/linear-release/src/tests.rs b/pallets/linear-release/src/tests.rs index 5df508c4e..2a5628212 100644 --- a/pallets/linear-release/src/tests.rs +++ b/pallets/linear-release/src/tests.rs @@ -1498,13 +1498,7 @@ fn check_external_release_behavior() { // assert_eq!(Vesting::vesting_balance(&3, LockType::Participation(0)), None); // Set release schedule to release the locked amount, starting from now, one ED per block. - assert_ok!(Vesting::set_release_schedule( - &3, - user_3_on_hold_balance, - ED, - 0, - LockType::Participation(0) - )); + assert_ok!(Vesting::set_release_schedule(&3, user_3_on_hold_balance, ED, 0, LockType::Participation(0))); let user_3_on_hold_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); assert_eq!(user_3_on_hold_balance, 4 * ED); diff --git a/runtimes/testnet/src/xcm_config.rs b/runtimes/testnet/src/xcm_config.rs index 3d7a63c99..17302ce1b 100644 --- a/runtimes/testnet/src/xcm_config.rs +++ b/runtimes/testnet/src/xcm_config.rs @@ -66,7 +66,7 @@ parameter_types! { pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorMultiLocation = ( GlobalConsensus(Polkadot), - Parachain(ParachainInfo::parachain_id().into()), + Parachain(ParachainInfo::parachain_id().into()), ).into(); pub const HereLocation: MultiLocation = MultiLocation::here(); diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 350e0900d..f8fd6f2a1 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -36,12 +36,8 @@ pub trait ReleaseSchedule { reason: Reason, ) -> Option<>::Balance>; - /// Release the vested amount of the given account. - fn vest( - who: AccountId, - reason: Reason, - ) -> DispatchResult; + fn vest(who: AccountId, reason: Reason) -> DispatchResult; /// Adds a release schedule to a given account. /// From a547686bbc23723225276b158dcaf9a28318a1cd Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Tue, 8 Aug 2023 13:41:37 +0200 Subject: [PATCH 04/14] feat(229): bid vest scheduling tested. Vesting trait had wrong assumptions about one function. One new method added for checking total balance locked. Vest info now calculated when scheduling instead at the time of bidding, since the plmc locked could be reduced if the WAvP is lower than the bid price --- pallets/funding/src/functions.rs | 32 ++++++++++------ pallets/funding/src/impls.rs | 24 +++++------- pallets/funding/src/lib.rs | 7 +++- pallets/funding/src/tests.rs | 58 ++++++++++++++++++++++++++++- pallets/funding/src/types.rs | 5 +-- pallets/linear-release/src/impls.rs | 13 ++++++- traits/src/lib.rs | 11 +++++- 7 files changed, 115 insertions(+), 35 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 71fd37ec4..83643f860 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -37,9 +37,9 @@ use parachains_common::BlockNumber; use sp_arithmetic::Perquintill; +use polimec_traits::ReleaseSchedule; use sp_arithmetic::traits::{CheckedDiv, CheckedSub, Zero}; use sp_std::prelude::*; -use polimec_traits::ReleaseSchedule; // Round transition functions impl Pallet { @@ -109,7 +109,7 @@ impl Pallet { total_bonded_plmc: Zero::zero(), evaluators_outcome: EvaluatorsOutcome::Unchanged, }, - funding_end_block: None + funding_end_block: None, }; let project_metadata = initial_metadata; @@ -906,8 +906,6 @@ impl Pallet { // * Calculate new variables * let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price).map_err(|_| Error::::BadMath)?; - let plmc_vesting_info = - Self::calculate_vesting_info(bidder.clone(), multiplier, plmc_bond).map_err(|_| Error::::BadMath)?; let funding_asset_amount_locked = funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); @@ -926,7 +924,7 @@ impl Pallet { funding_asset_amount_locked, multiplier, plmc_bond, - plmc_vesting_info, + plmc_vesting_info: None, funded: false, when: now, funds_released: false, @@ -1365,19 +1363,26 @@ impl Pallet { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; let mut bid = Bids::::get((project_id, bidder.clone(), bid_id)).ok_or(Error::::BidNotFound)?; let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; - let vest_info = bid.plmc_vesting_info.clone(); // * Validity checks * ensure!( - !bid.plmc_vesting_info.scheduled && project_details.status == ProjectStatus::FundingSuccessful, + matches!(bid.plmc_vesting_info, None) && project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed ); // * Calculate variables * - bid.plmc_vesting_info.scheduled = true; + let vest_info = Self::calculate_vesting_info(bidder.clone(), bid.multiplier, bid.plmc_bond) + .map_err(|_| Error::::BadMath)?; + bid.plmc_vesting_info = Some(vest_info); // * Update storage * - T::Vesting::add_release_schedule(&bidder, vest_info.total_amount, vest_info.amount_per_block, funding_end_block, LockType::Participation(project_id))?; + T::Vesting::add_release_schedule( + &bidder, + vest_info.total_amount, + vest_info.amount_per_block, + funding_end_block, + LockType::Participation(project_id), + )?; Bids::::insert((project_id, bidder.clone(), bid_id), bid); // * Emit events * @@ -1386,7 +1391,7 @@ impl Pallet { bidder: bidder.clone(), id: bid_id, amount: vest_info.total_amount, - caller + caller, }); Ok(()) @@ -1445,7 +1450,6 @@ impl Pallet { ) -> Result<(), DispatchError> { Ok(()) } - } // Helper functions @@ -1505,7 +1509,11 @@ impl Pallet { let duration: u32 = 7u32 * parachains_common::DAYS; let amount_per_block = bonded_amount.checked_div(&duration.into()).ok_or(Error::::BadMath)?; - Ok(VestingInfo { total_amount: bonded_amount, amount_per_block, duration: >::from(duration), scheduled: false }) + Ok(VestingInfo { + total_amount: bonded_amount, + amount_per_block, + duration: >::from(duration), + }) } /// Calculates the price (in USD) of contribution tokens for the Community and Remainder Rounds diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 0e639bbe8..0215a39ad 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -50,10 +50,7 @@ impl DoRemainingOperation for CleanerState { }, CleanerState::EvaluationUnbonding(remaining, PhantomData) => if *remaining == 0 { - *self = CleanerState::StartBidderVestingSchedule( - remaining_bids::(project_id), - PhantomData, - ); + *self = CleanerState::StartBidderVestingSchedule(remaining_bids::(project_id), PhantomData); Ok(Weight::zero()) } else { let (consumed_weight, remaining_evaluations) = unbond_one_evaluation::(project_id); @@ -481,15 +478,17 @@ fn unbond_one_contribution(project_id: T::ProjectIdentifier) -> (Weig fn start_one_bid_vesting_schedule(project_id: T::ProjectIdentifier) -> (Weight, u64) { let project_bids = Bids::::iter_prefix_values((project_id,)); - let mut unscheduled_bids = project_bids.filter(|bid| !bid.plmc_vesting_info.scheduled); + let mut unscheduled_bids = project_bids.filter(|bid| matches!(bid.plmc_vesting_info, None)); if let Some(mut bid) = unscheduled_bids.next() { - match Pallet::::do_start_bid_vesting_schedule_for(T::PalletId::get().into_account_truncating(), project_id, bid.bidder.clone(), bid.id) { - Ok(_) => { - bid.plmc_vesting_info.scheduled = true; - Bids::::insert((project_id, bid.bidder.clone(), bid.id), bid); - }, - Err(e) => { + match Pallet::::do_start_bid_vesting_schedule_for( + T::PalletId::get().into_account_truncating(), + project_id, + bid.bidder.clone(), + bid.id, + ) { + Ok(_) => {}, + Err(e) => { Pallet::::deposit_event(Event::StartBidderVestingScheduleFailed { project_id: bid.project_id, bidder: bid.bidder.clone(), @@ -500,12 +499,9 @@ fn start_one_bid_vesting_schedule(project_id: T::ProjectIdentifier) - } (Weight::zero(), unscheduled_bids.count() as u64) - } else { (Weight::zero(), 0u64) } - - } fn start_one_contribution_vesting_schedule(_project_id: T::ProjectIdentifier) -> (Weight, u64) { diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 4e9ac2bc6..39ab9f99f 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -376,7 +376,12 @@ pub mod pallet { type EvaluationSuccessThreshold: Get; - type Vesting: polimec_traits::ReleaseSchedule, BondTypeOf, Currency=Self::NativeCurrency, Moment=BlockNumberOf>; + type Vesting: polimec_traits::ReleaseSchedule< + AccountIdOf, + BondTypeOf, + Currency = Self::NativeCurrency, + Moment = BlockNumberOf, + >; /// For now we expect 3 days until the project is automatically accepted. Timeline decided by MiCA regulations. type ManualAcceptanceDuration: Get; /// For now we expect 4 days from acceptance to settlement due to MiCA regulations. diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 5a0094533..e8da7ad7a 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// If you feel like getting in touch with us, you can do so at info@polimec.org +// If you feel like getting in touch with us, you ca ,n do so at info@polimec.org //! Tests for Funding pallet. use super::*; @@ -116,7 +116,7 @@ type BidInfoFilterOf = BidInfoFilter< ::AccountId, MultiplierOf, BlockNumberOf, - VestingInfoOf, + Option>, >; impl Default for BidInfoFilterOf { fn default() -> Self { @@ -2047,6 +2047,8 @@ mod evaluation_round_failure { mod auction_round_success { use super::*; + use polimec_traits::ReleaseSchedule; + use std::ops::Div; #[test] fn auction_round_completed() { @@ -2674,6 +2676,58 @@ mod auction_round_success { }) } } + + #[test] + pub fn plmc_vesting_schedule_starts_automatically() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + + let mut bids = default_bids(); + let median_price = bids[bids.len().div(2)].price; + let new_bids = vec![ + TestBid::new(BIDDER_4, 30_000 * US_DOLLAR, median_price, None, AcceptedFundingAsset::USDT), + TestBid::new(BIDDER_5, 167_000 * US_DOLLAR, median_price, None, AcceptedFundingAsset::USDT), + ]; + bids.extend(new_bids.clone()); + + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions, + ); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let final_price = details.weighted_average_price.unwrap(); + let plmc_locked_for_bids = calculate_auction_plmc_spent_after_price_calculation(new_bids, final_price); + + for (user, amount) in plmc_locked_for_bids { + let schedule = test_env.in_ext(|| { + ::Vesting::total_scheduled_amount( + &user, + LockType::Participation(finished_project.project_id), + ) + }); + + assert_eq!(schedule.unwrap(), amount); + } + } + + #[test] + pub fn plmc_vesting_works() { + assert!(true); + } } mod auction_round_failure { diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 9ee91119b..b7d7c8376 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -64,7 +64,7 @@ pub mod config_types { #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo, Ord, PartialOrd)] pub enum ParticipationType { Bid(StorageItemId), - Contribution(StorageItemId) + Contribution(StorageItemId), } pub struct ConstPriceProvider(PhantomData<(AssetId, Price, Mapping)>); @@ -199,7 +199,7 @@ pub mod storage_types { pub funding_asset_amount_locked: Balance, pub multiplier: Multiplier, pub plmc_bond: Balance, - pub plmc_vesting_info: VestingInfo, + pub plmc_vesting_info: Option, pub funded: bool, pub when: BlockNumber, pub funds_released: bool, @@ -445,7 +445,6 @@ pub mod inner_types { pub total_amount: Balance, pub amount_per_block: Balance, pub duration: BlockNumber, - pub scheduled: bool } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] diff --git a/pallets/linear-release/src/impls.rs b/pallets/linear-release/src/impls.rs index 7272335c8..47523d0a8 100644 --- a/pallets/linear-release/src/impls.rs +++ b/pallets/linear-release/src/impls.rs @@ -240,7 +240,7 @@ impl ReleaseSchedule, ReasonOf> for Pallet { type Currency = T::Currency; type Moment = BlockNumberFor; - /// Get the amount that is currently being held and cannot be transferred out of this account. + /// Get the amount that is possible to vest (i.e release) at this block. fn vesting_balance(who: &T::AccountId, reason: ReasonOf) -> Option> { if let Some(v) = Self::vesting(who, reason) { let now = >::block_number(); @@ -253,6 +253,17 @@ impl ReleaseSchedule, ReasonOf> for Pallet { } } + fn total_scheduled_amount(who: &T::AccountId, reason: ReasonOf) -> Option> { + if let Some(v) = Self::vesting(who, reason) { + let total = v.iter().fold(Zero::zero(), |total, schedule| { + schedule.locked.saturating_add(total) + }); + Some(total) + } else { + None + } + } + /// Adds a vesting schedule to a given account. /// /// If the account has `MaxVestingSchedules`, an Error is returned and nothing diff --git a/traits/src/lib.rs b/traits/src/lib.rs index f8fd6f2a1..465728fa3 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -29,13 +29,20 @@ pub trait ReleaseSchedule { + fungible::MutateHold + fungible::BalancedHold; - /// Get the amount that is currently being vested and cannot be transferred out of this account. - /// Returns `None` if the account has no vesting schedule. + /// Get the amount that is possible to vest (i.e release) at the current block fn vesting_balance( who: &AccountId, reason: Reason, ) -> Option<>::Balance>; + /// Get the amount that was scheduled, regardless if it was already vested or not + fn total_scheduled_amount( + who: &AccountId, + reason: Reason, + ) -> Option<>::Balance>; + + + /// Release the vested amount of the given account. fn vest(who: AccountId, reason: Reason) -> DispatchResult; From 9c6f81732d7b74b15ff76e4d3250bd170c11fa91 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Tue, 8 Aug 2023 14:24:55 +0200 Subject: [PATCH 05/14] wip --- pallets/funding/src/functions.rs | 28 +++++++++++++++++++++++- pallets/funding/src/impls.rs | 2 +- pallets/funding/src/lib.rs | 6 ++++++ pallets/funding/src/tests.rs | 37 +++++++++++++++++++++++++++++++- pallets/funding/src/types.rs | 2 +- traits/src/lib.rs | 2 +- 6 files changed, 72 insertions(+), 5 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 83643f860..91b34927b 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -33,7 +33,6 @@ use frame_support::{ Get, }, }; -use parachains_common::BlockNumber; use sp_arithmetic::Perquintill; @@ -1397,6 +1396,33 @@ impl Pallet { Ok(()) } + pub fn do_vest_plmc_for(caller: AccountIdOf, project_id: T::ProjectIdentifier, participant: AccountIdOf) -> DispatchResult { + // * Get variables * + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; + + // * Validity checks * + ensure!( + matches!(project_details.status, ProjectStatus::FundingSuccessful), + Error::::NotAllowed + ); + + // * Update storage * + let vested_amount = T::Vesting::vest( + participant.clone(), + LockType::Participation(project_id), + )?; + + // * Emit events * + Self::deposit_event(Event::::ParticipantPlmcVested { + project_id, + participant: participant.clone(), + amount: vested_amount, + caller, + }); + + Ok(()) + } + pub fn do_release_bid_funds_for( _caller: AccountIdOf, _project_id: T::ProjectIdentifier, diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 0215a39ad..998f719f2 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -480,7 +480,7 @@ fn start_one_bid_vesting_schedule(project_id: T::ProjectIdentifier) - let project_bids = Bids::::iter_prefix_values((project_id,)); let mut unscheduled_bids = project_bids.filter(|bid| matches!(bid.plmc_vesting_info, None)); - if let Some(mut bid) = unscheduled_bids.next() { + if let Some(bid) = unscheduled_bids.next() { match Pallet::::do_start_bid_vesting_schedule_for( T::PalletId::get().into_account_truncating(), project_id, diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 39ab9f99f..de4d0acca 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -633,6 +633,12 @@ pub mod pallet { amount: BalanceOf, caller: AccountIdOf, }, + ParticipantPlmcVested { + project_id: ProjectIdOf, + participant: AccountIdOf, + amount: BalanceOf, + caller: AccountIdOf, + }, } #[pallet::error] diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index e8da7ad7a..bdfbd71f9 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -2726,7 +2726,42 @@ mod auction_round_success { #[test] pub fn plmc_vesting_works() { - assert!(true); + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + + let mut bids = default_bids(); + let median_price = bids[bids.len().div(2)].price; + let new_bids = vec![ + TestBid::new(BIDDER_4, 30_000 * US_DOLLAR, median_price, None, AcceptedFundingAsset::USDT), + TestBid::new(BIDDER_5, 167_000 * US_DOLLAR, median_price, None, AcceptedFundingAsset::USDT), + ]; + bids.extend(new_bids.clone()); + + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions, + ); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let final_price = details.weighted_average_price.unwrap(); + let plmc_locked_for_bids = calculate_auction_plmc_spent_after_price_calculation(new_bids, final_price); + let bidders = plmc_locked_for_bids.clone().into_iter().map(|(acc, amount)| acc).collect::>(); + let free_plmc_on_bidders = test_env.get_free_plmc_balances_for(bidders); + + } } diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index b7d7c8376..200696193 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -23,7 +23,7 @@ use crate::{ BalanceOf, }; use frame_support::{pallet_prelude::*, traits::tokens::Balance as BalanceT}; -use sp_arithmetic::{traits::Saturating, FixedPointNumber, FixedPointOperand}; +use sp_arithmetic::{FixedPointNumber, FixedPointOperand}; use sp_runtime::traits::CheckedDiv; use sp_std::{cmp::Eq, collections::btree_map::*, prelude::*}; diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 465728fa3..4d31a423c 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -44,7 +44,7 @@ pub trait ReleaseSchedule { /// Release the vested amount of the given account. - fn vest(who: AccountId, reason: Reason) -> DispatchResult; + fn vest(who: AccountId, reason: Reason) -> Result<>::Balance, DispatchError>; /// Adds a release schedule to a given account. /// From e7dc24051fc11be383d47277eae5eb5697a21b60 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Tue, 8 Aug 2023 14:25:38 +0200 Subject: [PATCH 06/14] fix(229): VestingUpdated event was displaying the vested instead of the unvested amount --- pallets/linear-release/src/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/linear-release/src/impls.rs b/pallets/linear-release/src/impls.rs index 47523d0a8..5bb50f92a 100644 --- a/pallets/linear-release/src/impls.rs +++ b/pallets/linear-release/src/impls.rs @@ -159,7 +159,7 @@ impl Pallet { let already_held = T::Currency::balance_on_hold(&reason, who); let to_release = already_held.saturating_sub(total_held_now); T::Currency::release(&reason, who, to_release, Precision::BestEffort)?; - Self::deposit_event(Event::::VestingUpdated { account: who.clone(), unvested: to_release }); + Self::deposit_event(Event::::VestingUpdated { account: who.clone(), unvested: total_held_now }); }; Ok(()) From 59332eef0a3d38378e3702a096aa28c6002b284c Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Tue, 8 Aug 2023 15:43:15 +0200 Subject: [PATCH 07/14] feat(229): Full and partial vesting of PLMC for bids implemented and tested --- pallets/funding/src/functions.rs | 16 +++--- pallets/funding/src/tests.rs | 83 ++++++++++++++++++++++++----- pallets/linear-release/src/impls.rs | 22 ++++---- traits/src/lib.rs | 7 +-- 4 files changed, 92 insertions(+), 36 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 91b34927b..d478d8398 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -1396,21 +1396,19 @@ impl Pallet { Ok(()) } - pub fn do_vest_plmc_for(caller: AccountIdOf, project_id: T::ProjectIdentifier, participant: AccountIdOf) -> DispatchResult { + pub fn do_vest_plmc_for( + caller: AccountIdOf, + project_id: T::ProjectIdentifier, + participant: AccountIdOf, + ) -> DispatchResult { // * Get variables * let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; // * Validity checks * - ensure!( - matches!(project_details.status, ProjectStatus::FundingSuccessful), - Error::::NotAllowed - ); + ensure!(matches!(project_details.status, ProjectStatus::FundingSuccessful), Error::::NotAllowed); // * Update storage * - let vested_amount = T::Vesting::vest( - participant.clone(), - LockType::Participation(project_id), - )?; + let vested_amount = T::Vesting::vest(participant.clone(), LockType::Participation(project_id))?; // * Emit events * Self::deposit_event(Event::::ParticipantPlmcVested { diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index bdfbd71f9..709ae404d 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -2049,6 +2049,9 @@ mod auction_round_success { use super::*; use polimec_traits::ReleaseSchedule; use std::ops::Div; + use frame_support::traits::fungible::Inspect; + use parachains_common::DAYS; + use crate::tests::testing_macros::extract_from_event; #[test] fn auction_round_completed() { @@ -2725,20 +2728,58 @@ mod auction_round_success { } #[test] - pub fn plmc_vesting_works() { + pub fn plmc_vesting_full_amount() { let test_env = TestEnvironment::new(); let issuer = ISSUER; let project = default_project(test_env.get_new_nonce()); let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; - let mut bids = default_bids(); - let median_price = bids[bids.len().div(2)].price; - let new_bids = vec![ - TestBid::new(BIDDER_4, 30_000 * US_DOLLAR, median_price, None, AcceptedFundingAsset::USDT), - TestBid::new(BIDDER_5, 167_000 * US_DOLLAR, median_price, None, AcceptedFundingAsset::USDT), - ]; - bids.extend(new_bids.clone()); + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions, + ); + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let stored_bids = test_env + .in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); + + test_env.advance_time((31 * DAYS).into()).unwrap(); + + for bid in stored_bids { + let vesting_info = bid.plmc_vesting_info.unwrap(); + let locked_amount = vesting_info.total_amount; + + let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&bid.bidder)); + + test_env.in_ext(|| Pallet::::do_vest_plmc_for( + bid.bidder.clone(), + finished_project.project_id, + bid.bidder.clone(), + )).unwrap(); + + let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&bid.bidder)); + assert_eq!(locked_amount, post_free_balance - prev_free_balance); + } + } + + #[test] + pub fn plmc_vesting_partial_amount() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); let community_contributions = default_community_buys(); let remainder_contributions = vec![]; @@ -2752,16 +2793,32 @@ mod auction_round_success { remainder_contributions, ); - test_env.advance_time(10u64).unwrap(); + test_env.advance_time(15u64).unwrap(); let details = finished_project.get_project_details(); assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + let vest_start_block = details.funding_end_block.unwrap(); + let stored_bids = test_env + .in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); - let final_price = details.weighted_average_price.unwrap(); - let plmc_locked_for_bids = calculate_auction_plmc_spent_after_price_calculation(new_bids, final_price); - let bidders = plmc_locked_for_bids.clone().into_iter().map(|(acc, amount)| acc).collect::>(); - let free_plmc_on_bidders = test_env.get_free_plmc_balances_for(bidders); + for bid in stored_bids { + let vesting_info = bid.plmc_vesting_info.unwrap(); + let locked_amount = vesting_info.total_amount; + + let now = test_env.current_block(); + let blocks_passed = now - vest_start_block; + let vested_amount = vesting_info.amount_per_block * blocks_passed as u128; + let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&bid.bidder)); + test_env.in_ext(|| Pallet::::do_vest_plmc_for( + bid.bidder.clone(), + finished_project.project_id, + bid.bidder.clone(), + )).unwrap(); + + let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&bid.bidder)); + assert_eq!(vested_amount, post_free_balance - prev_free_balance); + } } } diff --git a/pallets/linear-release/src/impls.rs b/pallets/linear-release/src/impls.rs index 5bb50f92a..f0dd5aeb4 100644 --- a/pallets/linear-release/src/impls.rs +++ b/pallets/linear-release/src/impls.rs @@ -148,12 +148,7 @@ impl Pallet { reason: ReasonOf, ) -> Result<(), DispatchError> { if total_held_now.is_zero() { - T::Currency::release( - &reason, - who, - T::Currency::balance_on_hold(&reason, who), - frame_support::traits::tokens::Precision::BestEffort, - )?; + T::Currency::release(&reason, who, T::Currency::balance_on_hold(&reason, who), Precision::BestEffort)?; Self::deposit_event(Event::::VestingCompleted { account: who.clone() }); } else { let already_held = T::Currency::balance_on_hold(&reason, who); @@ -255,9 +250,7 @@ impl ReleaseSchedule, ReasonOf> for Pallet { fn total_scheduled_amount(who: &T::AccountId, reason: ReasonOf) -> Option> { if let Some(v) = Self::vesting(who, reason) { - let total = v.iter().fold(Zero::zero(), |total, schedule| { - schedule.locked.saturating_add(total) - }); + let total = v.iter().fold(Zero::zero(), |total, schedule| schedule.locked.saturating_add(total)); Some(total) } else { None @@ -369,7 +362,14 @@ impl ReleaseSchedule, ReasonOf> for Pallet { Ok(()) } - fn vest(who: AccountIdOf, reason: ReasonOf) -> DispatchResult { - Self::do_vest(who, reason) + fn vest( + who: AccountIdOf, + reason: ReasonOf, + ) -> Result<>::Balance, DispatchError> { + let prev_locked = T::Currency::balance_on_hold(&reason, &who); + Self::do_vest(who.clone(), reason.clone())?; + let post_locked = T::Currency::balance_on_hold(&reason, &who); + + Ok(post_locked.saturating_sub(prev_locked)) } } diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 4d31a423c..94367d778 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -41,10 +41,11 @@ pub trait ReleaseSchedule { reason: Reason, ) -> Option<>::Balance>; - - /// Release the vested amount of the given account. - fn vest(who: AccountId, reason: Reason) -> Result<>::Balance, DispatchError>; + fn vest( + who: AccountId, + reason: Reason, + ) -> Result<>::Balance, DispatchError>; /// Adds a release schedule to a given account. /// From 25761eebd3e25968e9364f7cb77a78270360e5b7 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Tue, 8 Aug 2023 16:09:09 +0200 Subject: [PATCH 08/14] feat(229): Full and partial vesting of PLMC for contributions implemented and tested. Automatic vesting scheduling for contributions implemented and tested --- pallets/funding/src/functions.rs | 48 ++++++++++- pallets/funding/src/impls.rs | 29 ++++++- pallets/funding/src/lib.rs | 15 +++- pallets/funding/src/tests.rs | 138 ++++++++++++++++++++++++++++++- pallets/funding/src/types.rs | 5 +- 5 files changed, 224 insertions(+), 11 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index d478d8398..ab91a0b1d 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -1031,7 +1031,6 @@ impl Pallet { remaining_amount }; let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price)?; - let plmc_vesting_info = Self::calculate_vesting_info(contributor.clone(), multiplier.clone(), plmc_bond)?; let funding_asset_amount = funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); let asset_id = asset.to_statemint_id(); @@ -1043,10 +1042,11 @@ impl Pallet { contributor: contributor.clone(), ct_amount: token_amount, usd_contribution_amount: ticket_size, + multiplier, funding_asset: asset, funding_asset_amount, plmc_bond, - plmc_vesting_info, + plmc_vesting_info: None, funds_released: false, ct_minted: false, }; @@ -1396,6 +1396,50 @@ impl Pallet { Ok(()) } + pub fn do_start_contribution_vesting_schedule_for( + caller: AccountIdOf, + project_id: T::ProjectIdentifier, + contributor: AccountIdOf, + contribution_id: StorageItemIdOf, + ) -> Result<(), DispatchError> { + // * Get variables * + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; + let mut contribution = Contributions::::get((project_id, contributor.clone(), contribution_id)).ok_or(Error::::BidNotFound)?; + let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; + + // * Validity checks * + ensure!( + matches!(contribution.plmc_vesting_info, None) && project_details.status == ProjectStatus::FundingSuccessful, + Error::::NotAllowed + ); + + // * Calculate variables * + let vest_info = Self::calculate_vesting_info(contributor.clone(), contribution.multiplier, contribution.plmc_bond) + .map_err(|_| Error::::BadMath)?; + contribution.plmc_vesting_info = Some(vest_info); + + // * Update storage * + T::Vesting::add_release_schedule( + &contributor, + vest_info.total_amount, + vest_info.amount_per_block, + funding_end_block, + LockType::Participation(project_id), + )?; + Contributions::::insert((project_id, contributor.clone(), contribution_id), contribution); + + // * Emit events * + Self::deposit_event(Event::::ContributionPlmcVestingScheduled { + project_id, + contributor: contributor.clone(), + id: contribution_id, + amount: vest_info.total_amount, + caller, + }); + + Ok(()) + } + pub fn do_vest_plmc_for( caller: AccountIdOf, project_id: T::ProjectIdentifier, diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 998f719f2..273c661ce 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -504,9 +504,32 @@ fn start_one_bid_vesting_schedule(project_id: T::ProjectIdentifier) - } } -fn start_one_contribution_vesting_schedule(_project_id: T::ProjectIdentifier) -> (Weight, u64) { - // TODO: change when new vesting schedule is implemented - (Weight::zero(), 0u64) +fn start_one_contribution_vesting_schedule(project_id: T::ProjectIdentifier) -> (Weight, u64) { + let project_bids = Contributions::::iter_prefix_values((project_id,)); + let mut unscheduled_contributions = project_bids.filter(|contribution| matches!(contribution.plmc_vesting_info, None)); + + if let Some(contribution) = unscheduled_contributions.next() { + match Pallet::::do_start_contribution_vesting_schedule_for( + T::PalletId::get().into_account_truncating(), + project_id, + contribution.contributor.clone(), + contribution.id, + ) { + Ok(_) => {}, + Err(e) => { + Pallet::::deposit_event(Event::StartContributionVestingScheduleFailed { + project_id: contribution.project_id, + contributor: contribution.contributor.clone(), + id: contribution.id, + error: e, + }); + }, + } + + (Weight::zero(), unscheduled_contributions.count() as u64) + } else { + (Weight::zero(), 0u64) + } } fn mint_ct_for_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) { diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index de4d0acca..05775e45c 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -245,7 +245,7 @@ pub type BidInfoOf = BidInfo< VestingInfoOf, >; pub type ContributionInfoOf = - ContributionInfo, ProjectIdOf, AccountIdOf, BalanceOf, VestingInfoOf>; + ContributionInfo, ProjectIdOf, AccountIdOf, BalanceOf, MultiplierOf, VestingInfoOf>; pub type BondTypeOf = LockType>; const PLMC_STATEMINT_ID: u32 = 2069; @@ -626,6 +626,12 @@ pub mod pallet { id: StorageItemIdOf, error: DispatchError, }, + StartContributionVestingScheduleFailed { + project_id: ProjectIdOf, + contributor: AccountIdOf, + id: StorageItemIdOf, + error: DispatchError, + }, BidPlmcVestingScheduled { project_id: ProjectIdOf, bidder: AccountIdOf, @@ -633,6 +639,13 @@ pub mod pallet { amount: BalanceOf, caller: AccountIdOf, }, + ContributionPlmcVestingScheduled { + project_id: ProjectIdOf, + contributor: AccountIdOf, + id: StorageItemIdOf, + amount: BalanceOf, + caller: AccountIdOf, + }, ParticipantPlmcVested { project_id: ProjectIdOf, participant: AccountIdOf, diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 709ae404d..a53cab3c1 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -2051,7 +2051,6 @@ mod auction_round_success { use std::ops::Div; use frame_support::traits::fungible::Inspect; use parachains_common::DAYS; - use crate::tests::testing_macros::extract_from_event; #[test] fn auction_round_completed() { @@ -2754,7 +2753,7 @@ mod auction_round_success { let stored_bids = test_env .in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); - test_env.advance_time((31 * DAYS).into()).unwrap(); + test_env.advance_time((10 * DAYS).into()).unwrap(); for bid in stored_bids { let vesting_info = bid.plmc_vesting_info.unwrap(); @@ -2802,7 +2801,6 @@ mod auction_round_success { for bid in stored_bids { let vesting_info = bid.plmc_vesting_info.unwrap(); - let locked_amount = vesting_info.total_amount; let now = test_env.current_block(); let blocks_passed = now - vest_start_block; @@ -3072,6 +3070,8 @@ mod community_round_success { use super::*; use frame_support::traits::fungible::Inspect; use std::assert_matches::assert_matches; + use parachains_common::DAYS; + use polimec_traits::ReleaseSchedule; pub const HOURS: BlockNumber = 300u64; @@ -3891,6 +3891,138 @@ mod community_round_success { }) } } + + #[test] + pub fn plmc_vesting_schedule_starts_automatically() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions.clone(), + remainder_contributions, + ); + + let price = finished_project.get_project_details().weighted_average_price.unwrap(); + let contribution_locked_plmc = calculate_contributed_plmc_spent(community_contributions, price); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + for (user, amount) in contribution_locked_plmc { + let schedule = test_env.in_ext(|| { + ::Vesting::total_scheduled_amount( + &user, + LockType::Participation(finished_project.project_id), + ) + }); + + assert_eq!(schedule.unwrap(), amount); + } + } + + #[test] + pub fn plmc_vesting_full_amount() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions, + ); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let stored_contributions = test_env + .in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + + test_env.advance_time((10 * DAYS).into()).unwrap(); + + for contribution in stored_contributions { + let vesting_info = contribution.plmc_vesting_info.unwrap(); + let locked_amount = vesting_info.total_amount; + + let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); + + test_env.in_ext(|| Pallet::::do_vest_plmc_for( + contribution.contributor.clone(), + finished_project.project_id, + contribution.contributor.clone(), + )).unwrap(); + + let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); + assert_eq!(locked_amount, post_free_balance - prev_free_balance); + } + } + + #[test] + pub fn plmc_vesting_partial_amount() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions, + ); + + test_env.advance_time(15u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + let vest_start_block = details.funding_end_block.unwrap(); + let stored_contributions = test_env + .in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + + for contribution in stored_contributions { + let vesting_info = contribution.plmc_vesting_info.unwrap(); + + let now = test_env.current_block(); + let blocks_passed = now - vest_start_block; + let vested_amount = vesting_info.amount_per_block * blocks_passed as u128; + + let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); + + test_env.in_ext(|| Pallet::::do_vest_plmc_for( + contribution.contributor.clone(), + finished_project.project_id, + contribution.contributor.clone(), + )).unwrap(); + + let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); + assert_eq!(vested_amount, post_free_balance - prev_free_balance); + } + } } mod remainder_round_success { diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 200696193..4b94efab8 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -242,16 +242,17 @@ pub mod storage_types { } #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] - pub struct ContributionInfo { + pub struct ContributionInfo { pub id: Id, pub project_id: ProjectId, pub contributor: AccountId, pub ct_amount: Balance, pub usd_contribution_amount: Balance, + pub multiplier: Multiplier, pub funding_asset: AcceptedFundingAsset, pub funding_asset_amount: Balance, pub plmc_bond: Balance, - pub plmc_vesting_info: VestingInfo, + pub plmc_vesting_info: Option, pub funds_released: bool, pub ct_minted: bool, } From 49d124a657f1c5bbd47df6bce4eb91bb40907c54 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Tue, 8 Aug 2023 16:24:55 +0200 Subject: [PATCH 09/14] feat(229): manual start of vesting schedule implemented and tested --- pallets/funding/src/lib.rs | 23 ++++++++ pallets/funding/src/tests.rs | 106 +++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 05775e45c..f65f6b411 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -893,6 +893,29 @@ pub mod pallet { let caller = ensure_signed(origin)?; Self::do_contribution_ct_mint_for(caller, project_id, contributor, contribution_id) } + + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn start_bid_vesting_schedule_for( + origin: OriginFor, + project_id: T::ProjectIdentifier, + bidder: AccountIdOf, + bid_id: T::StorageItemId, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + Self::do_start_bid_vesting_schedule_for(caller, project_id, bidder, bid_id) + } + + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn start_contribution_vesting_schedule_for( + origin: OriginFor, + project_id: T::ProjectIdentifier, + contributor: AccountIdOf, + contribution_id: T::StorageItemId, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + Self::do_start_contribution_vesting_schedule_for(caller, project_id, contributor, contribution_id) + } + } #[pallet::hooks] diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index a53cab3c1..620fa619c 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -2051,6 +2051,7 @@ mod auction_round_success { use std::ops::Div; use frame_support::traits::fungible::Inspect; use parachains_common::DAYS; + use crate::tests::testing_macros::call_and_is_ok; #[test] fn auction_round_completed() { @@ -2726,6 +2727,58 @@ mod auction_round_success { } } + #[test] + pub fn plmc_vesting_schedule_starts_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids.clone(), + community_contributions, + remainder_contributions, + ); + + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + let stored_bids = test_env.in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); + for bid in stored_bids { + call_and_is_ok!( + test_env, + Pallet::::start_bid_vesting_schedule_for( + RuntimeOrigin::signed(bid.bidder), + finished_project.project_id, + bid.bidder, + bid.id, + ) + ); + + let schedule = test_env.in_ext(|| { + ::Vesting::total_scheduled_amount( + &bid.bidder, + LockType::Participation(finished_project.project_id), + ) + }); + + let bid = test_env.in_ext(|| Bids::::get((finished_project.project_id, bid.bidder, bid.id)).unwrap()); + assert_eq!(schedule.unwrap(), bid.plmc_vesting_info.unwrap().total_amount); + } + } + #[test] pub fn plmc_vesting_full_amount() { let test_env = TestEnvironment::new(); @@ -3072,6 +3125,7 @@ mod community_round_success { use std::assert_matches::assert_matches; use parachains_common::DAYS; use polimec_traits::ReleaseSchedule; + use crate::tests::testing_macros::call_and_is_ok; pub const HOURS: BlockNumber = 300u64; @@ -3931,6 +3985,58 @@ mod community_round_success { } } + #[test] + pub fn plmc_vesting_schedule_starts_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions.clone(), + remainder_contributions, + ); + + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + let contributions = test_env.in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + for contribution in contributions { + call_and_is_ok!( + test_env, + Pallet::::start_contribution_vesting_schedule_for( + RuntimeOrigin::signed(contribution.contributor), + finished_project.project_id, + contribution.contributor, + contribution.id, + ) + ); + + let schedule = test_env.in_ext(|| { + ::Vesting::total_scheduled_amount( + &contribution.contributor, + LockType::Participation(finished_project.project_id), + ) + }); + + let contribution = test_env.in_ext(|| Contributions::::get((finished_project.project_id, contribution.contributor, contribution.id)).unwrap()); + assert_eq!(schedule.unwrap(), contribution.plmc_vesting_info.unwrap().total_amount); + } + } + #[test] pub fn plmc_vesting_full_amount() { let test_env = TestEnvironment::new(); From 49a891082481dbb7337c46774a9594691fa94977 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Wed, 9 Aug 2023 17:28:58 +0200 Subject: [PATCH 10/14] feat(229): fixed linear release pallet, finished implementing vesting for remainder round --- pallets/funding/src/functions.rs | 3 +- pallets/funding/src/tests.rs | 217 +++++++++++++++++++++++++++- pallets/linear-release/src/impls.rs | 8 +- pallets/linear-release/src/tests.rs | 53 +------ 4 files changed, 224 insertions(+), 57 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index ab91a0b1d..90e6a5692 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -33,6 +33,7 @@ use frame_support::{ Get, }, }; +use frame_support::traits::fungible::InspectHold; use sp_arithmetic::Perquintill; @@ -1574,7 +1575,7 @@ impl Pallet { bonded_amount: BalanceOf, ) -> Result>, DispatchError> { // TODO: lock_time should depend on `_multiplier` and `_caller` credential - let duration: u32 = 7u32 * parachains_common::DAYS; + let duration: u32 = 1u32 * parachains_common::DAYS; let amount_per_block = bonded_amount.checked_div(&duration.into()).ok_or(Error::::BadMath)?; Ok(VestingInfo { diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 620fa619c..e9de4084a 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -43,6 +43,7 @@ use sp_runtime::{DispatchError, Either}; use sp_std::marker::PhantomData; use std::{cell::RefCell, iter::zip}; + type ProjectIdOf = ::ProjectIdentifier; type UserToPLMCBalance = Vec<(AccountId, BalanceOf)>; type UserToUSDBalance = Vec<(AccountId, BalanceOf)>; @@ -4132,8 +4133,11 @@ mod community_round_success { } mod remainder_round_success { + use frame_support::traits::fungible::Inspect; + use parachains_common::DAYS; + use polimec_traits::ReleaseSchedule; use super::*; - use crate::tests::testing_macros::extract_from_event; + use crate::tests::testing_macros::{call_and_is_ok, extract_from_event}; #[test] fn remainder_round_works() { @@ -4709,6 +4713,217 @@ mod remainder_round_success { }); } } + + #[test] + pub fn plmc_vesting_schedule_starts_automatically() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = default_remainder_buys(); + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids.clone(), + community_contributions.clone(), + remainder_contributions.clone(), + ); + + let price = finished_project.get_project_details().weighted_average_price.unwrap(); + let auction_locked_plmc = calculate_auction_plmc_spent_after_price_calculation(bids, price); + let community_locked_plmc = calculate_contributed_plmc_spent(community_contributions, price); + let remainder_locked_plmc = calculate_contributed_plmc_spent(remainder_contributions, price); + let all_plmc_locks = merge_add_mappings_by_user(vec![auction_locked_plmc, community_locked_plmc, remainder_locked_plmc]); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + for (user, amount) in all_plmc_locks { + let schedule = test_env.in_ext(|| { + ::Vesting::total_scheduled_amount( + &user, + LockType::Participation(finished_project.project_id), + ) + }); + + assert_eq!(schedule.unwrap(), amount); + } + } + + #[test] + pub fn plmc_vesting_schedule_starts_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = default_remainder_buys(); + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids.clone(), + community_contributions.clone(), + remainder_contributions.clone(), + ); + + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + let contributions = test_env.in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + for contribution in contributions { + let prev_scheduled = test_env.in_ext(|| { + ::Vesting::total_scheduled_amount( + &contribution.contributor, + LockType::Participation(finished_project.project_id), + ) + }).unwrap_or(Zero::zero()); + + call_and_is_ok!( + test_env, + Pallet::::start_contribution_vesting_schedule_for( + RuntimeOrigin::signed(contribution.contributor), + finished_project.project_id, + contribution.contributor, + contribution.id, + ) + ); + + let post_scheduled = test_env.in_ext(|| { + ::Vesting::total_scheduled_amount( + &contribution.contributor, + LockType::Participation(finished_project.project_id), + ) + }).unwrap(); + + let new_scheduled = post_scheduled - prev_scheduled; + + let contribution = test_env.in_ext(|| Contributions::::get((finished_project.project_id, contribution.contributor, contribution.id)).unwrap()); + assert_eq!(new_scheduled, contribution.plmc_vesting_info.unwrap().total_amount); + } + } + + #[test] + pub fn plmc_vesting_full_amount() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = default_remainder_buys(); + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions, + ); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let stored_bids = test_env.in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_contributions = test_env + .in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + + let bid_plmc_balances = stored_bids.into_iter().map(|b| (b.bidder, b.plmc_vesting_info.unwrap().total_amount)).collect::>(); + let contributed_plmc_balances = stored_contributions.into_iter().map(|c| (c.contributor, c.plmc_vesting_info.unwrap().total_amount)).collect::>(); + + let merged_plmc_balances = generic_map_merge_reduce( + vec![contributed_plmc_balances.clone(), bid_plmc_balances.clone()], + |(account, amount)| account.clone(), + BalanceOf::::zero(), + |(account, amount), total| total + amount + ); + test_env.advance_time((1 * DAYS + 1u32).into()).unwrap(); + + for (contributor, plmc_amount) in merged_plmc_balances { + let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contributor)); + test_env.in_ext(|| Pallet::::do_vest_plmc_for( + contributor.clone(), + finished_project.project_id, + contributor.clone(), + )).unwrap(); + + let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contributor)); + assert_eq!(plmc_amount, post_free_balance - prev_free_balance); + } + } + + #[test] + pub fn plmc_vesting_partial_amount() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = default_remainder_buys(); + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions, + ); + + test_env.advance_time(15u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + let vest_start_block = details.funding_end_block.unwrap(); + + let stored_bids = test_env.in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_contributions = test_env + .in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + + let now = test_env.current_block(); + let blocks_passed = now - vest_start_block; + + let bid_plmc_balances = stored_bids.into_iter().map(|b| (b.bidder, b.plmc_vesting_info.unwrap().amount_per_block * blocks_passed as u128)).collect::>(); + let contributed_plmc_balances = stored_contributions.into_iter().map(|c| (c.contributor, c.plmc_vesting_info.unwrap().amount_per_block * blocks_passed as u128)).collect::>(); + + let merged_plmc_balances = generic_map_merge_reduce( + vec![contributed_plmc_balances.clone(), bid_plmc_balances.clone()], + |(account, amount)| account.clone(), + BalanceOf::::zero(), + |(account, amount), total| total + amount + ); + + for (contributor, amount) in merged_plmc_balances { + let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contributor)); + + test_env.in_ext(|| Pallet::::do_vest_plmc_for( + contributor, + finished_project.project_id, + contributor, + )).unwrap(); + + let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contributor)); + assert_eq!(amount, post_free_balance - prev_free_balance); + } + } } mod funding_end { diff --git a/pallets/linear-release/src/impls.rs b/pallets/linear-release/src/impls.rs index f0dd5aeb4..085f53a49 100644 --- a/pallets/linear-release/src/impls.rs +++ b/pallets/linear-release/src/impls.rs @@ -97,7 +97,7 @@ impl Pallet { Self::deposit_event(Event::::VestingTransferred { to: target.clone(), amount: amount_transferred }); // We can't let this fail because the currency transfer has already happened. - let res = Self::add_release_schedule( + let res = Self::set_release_schedule( &target, amount_transferred, schedule.per_block(), @@ -269,7 +269,7 @@ impl ReleaseSchedule, ReasonOf> for Pallet { /// Is a no-op if the amount to be vested is zero. /// /// NOTE: This doesn't alter the free balance of the account. - fn add_release_schedule( + fn set_release_schedule( who: &T::AccountId, locked: BalanceOf, per_block: BalanceOf, @@ -321,7 +321,7 @@ impl ReleaseSchedule, ReasonOf> for Pallet { Ok(()) } - fn set_release_schedule( + fn add_release_schedule( who: &T::AccountId, locked: >::Balance, per_block: >::Balance, @@ -370,6 +370,6 @@ impl ReleaseSchedule, ReasonOf> for Pallet { Self::do_vest(who.clone(), reason.clone())?; let post_locked = T::Currency::balance_on_hold(&reason, &who); - Ok(post_locked.saturating_sub(prev_locked)) + Ok(prev_locked.saturating_sub(post_locked)) } } diff --git a/pallets/linear-release/src/tests.rs b/pallets/linear-release/src/tests.rs index 2a5628212..f46f3e7e3 100644 --- a/pallets/linear-release/src/tests.rs +++ b/pallets/linear-release/src/tests.rs @@ -1349,7 +1349,7 @@ fn vest_all_different_reason() { // Set release schedule to release the locked amount, starting from now, one ED per block. let user3_vesting_schedule = VestingInfo::new(user_3_on_hold_balance, ED, 0); - assert_ok!(Vesting::set_release_schedule( + assert_ok!(Vesting::add_release_schedule( &3, user3_vesting_schedule.locked, user3_vesting_schedule.per_block, @@ -1420,7 +1420,7 @@ fn manual_vest_all_different_reason() { // Set release schedule to release the locked amount, starting from now, one ED per block. let user3_vesting_schedule = VestingInfo::new(user_3_on_hold_balance, ED, 0); - assert_ok!(Vesting::set_release_schedule( + assert_ok!(Vesting::add_release_schedule( &3, user3_vesting_schedule.locked, user3_vesting_schedule.per_block, @@ -1473,52 +1473,3 @@ fn manual_vest_all_different_reason() { assert_eq!(user_3_free_balance, 30 * ED); }); } - -#[test] -#[ignore] -fn check_external_release_behavior() { - const ED: u64 = 10u64; - ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { - assert_eq!(System::block_number(), 1); - // Initial Status - let user_3_initial_free_balance = Balances::free_balance(&3); - assert_eq!(user_3_initial_free_balance, 30 * ED); // 7680 ED - let user_3_reserved_balance = Balances::reserved_balance(&3); - assert_eq!(user_3_reserved_balance, 0); - let user_3_on_hold_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); - assert_eq!(user_3_on_hold_balance, 0); - - assert_ok!(Balances::hold(&LockType::Participation(0), &3, 4 * ED)); - let user_3_on_hold_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); - let user_3_initial_free_balance = Balances::free_balance(&3); - assert_eq!(user_3_initial_free_balance, 26 * ED); // 7680 ED - assert_eq!(user_3_on_hold_balance, 4 * ED); - let user_3_reserved_balance = Balances::reserved_balance(&3); - assert_eq!(user_3_reserved_balance, 4 * ED); - // assert_eq!(Vesting::vesting_balance(&3, LockType::Participation(0)), None); - - // Set release schedule to release the locked amount, starting from now, one ED per block. - assert_ok!(Vesting::set_release_schedule(&3, user_3_on_hold_balance, ED, 0, LockType::Participation(0))); - - let user_3_on_hold_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); - assert_eq!(user_3_on_hold_balance, 4 * ED); - - // Release amount for block 0 to 3 - System::set_block_number(3); - assert_eq!(System::block_number(), 3); - assert_ok!(Vesting::vest(Some(3).into(), LockType::Participation(0))); - let free_balance = Balances::free_balance(&3); - let held_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); - assert_eq!(free_balance, 29 * ED); - assert_eq!(held_balance, 0 * ED); - - // Try releasing the remaining 2 ED, since the first release one was not available - System::set_block_number(4); - assert_eq!(System::block_number(), 4); - assert_ok!(Vesting::vest(Some(3).into(), LockType::Participation(0))); - let free_balance = Balances::free_balance(&3); - let held_balance = Balances::balance_on_hold(&LockType::Participation(0), &3); - assert_eq!(free_balance, 30 * ED); - assert_eq!(held_balance, 0 * ED); - }); -} From e8b66a051a5241f482a3e107ce5f768669ce3d92 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 10 Aug 2023 09:47:39 +0200 Subject: [PATCH 11/14] fix(229): fmt --- pallets/funding/src/functions.rs | 14 +- pallets/funding/src/impls.rs | 3 +- pallets/funding/src/lib.rs | 11 +- pallets/funding/src/tests.rs | 218 ++++++++++++++++++++----------- 4 files changed, 159 insertions(+), 87 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 90e6a5692..1dcc12b41 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -27,13 +27,12 @@ use frame_support::{ ensure, pallet_prelude::DispatchError, traits::{ - fungible::MutateHold as FungibleMutateHold, + fungible::{InspectHold, MutateHold as FungibleMutateHold}, fungibles::{metadata::Mutate as MetadataMutate, Create, Inspect, Mutate as FungiblesMutate}, tokens::{Fortitude, Precision, Preservation, Restriction}, Get, }, }; -use frame_support::traits::fungible::InspectHold; use sp_arithmetic::Perquintill; @@ -1405,18 +1404,21 @@ impl Pallet { ) -> Result<(), DispatchError> { // * Get variables * let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; - let mut contribution = Contributions::::get((project_id, contributor.clone(), contribution_id)).ok_or(Error::::BidNotFound)?; + let mut contribution = Contributions::::get((project_id, contributor.clone(), contribution_id)) + .ok_or(Error::::BidNotFound)?; let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; // * Validity checks * ensure!( - matches!(contribution.plmc_vesting_info, None) && project_details.status == ProjectStatus::FundingSuccessful, + matches!(contribution.plmc_vesting_info, None) && + project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed ); // * Calculate variables * - let vest_info = Self::calculate_vesting_info(contributor.clone(), contribution.multiplier, contribution.plmc_bond) - .map_err(|_| Error::::BadMath)?; + let vest_info = + Self::calculate_vesting_info(contributor.clone(), contribution.multiplier, contribution.plmc_bond) + .map_err(|_| Error::::BadMath)?; contribution.plmc_vesting_info = Some(vest_info); // * Update storage * diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 273c661ce..33d972c34 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -506,7 +506,8 @@ fn start_one_bid_vesting_schedule(project_id: T::ProjectIdentifier) - fn start_one_contribution_vesting_schedule(project_id: T::ProjectIdentifier) -> (Weight, u64) { let project_bids = Contributions::::iter_prefix_values((project_id,)); - let mut unscheduled_contributions = project_bids.filter(|contribution| matches!(contribution.plmc_vesting_info, None)); + let mut unscheduled_contributions = + project_bids.filter(|contribution| matches!(contribution.plmc_vesting_info, None)); if let Some(contribution) = unscheduled_contributions.next() { match Pallet::::do_start_contribution_vesting_schedule_for( diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index f65f6b411..f83772274 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -244,8 +244,14 @@ pub type BidInfoOf = BidInfo< MultiplierOf, VestingInfoOf, >; -pub type ContributionInfoOf = - ContributionInfo, ProjectIdOf, AccountIdOf, BalanceOf, MultiplierOf, VestingInfoOf>; +pub type ContributionInfoOf = ContributionInfo< + StorageItemIdOf, + ProjectIdOf, + AccountIdOf, + BalanceOf, + MultiplierOf, + VestingInfoOf, +>; pub type BondTypeOf = LockType>; const PLMC_STATEMINT_ID: u32 = 2069; @@ -915,7 +921,6 @@ pub mod pallet { let caller = ensure_signed(origin)?; Self::do_start_contribution_vesting_schedule_for(caller, project_id, contributor, contribution_id) } - } #[pallet::hooks] diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index e9de4084a..7667c544f 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -43,7 +43,6 @@ use sp_runtime::{DispatchError, Either}; use sp_std::marker::PhantomData; use std::{cell::RefCell, iter::zip}; - type ProjectIdOf = ::ProjectIdentifier; type UserToPLMCBalance = Vec<(AccountId, BalanceOf)>; type UserToUSDBalance = Vec<(AccountId, BalanceOf)>; @@ -2048,11 +2047,11 @@ mod evaluation_round_failure { mod auction_round_success { use super::*; - use polimec_traits::ReleaseSchedule; - use std::ops::Div; + use crate::tests::testing_macros::call_and_is_ok; use frame_support::traits::fungible::Inspect; use parachains_common::DAYS; - use crate::tests::testing_macros::call_and_is_ok; + use polimec_traits::ReleaseSchedule; + use std::ops::Div; #[test] fn auction_round_completed() { @@ -2756,7 +2755,8 @@ mod auction_round_success { let details = finished_project.get_project_details(); assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); - let stored_bids = test_env.in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_bids = test_env + .in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); for bid in stored_bids { call_and_is_ok!( test_env, @@ -2775,7 +2775,8 @@ mod auction_round_success { ) }); - let bid = test_env.in_ext(|| Bids::::get((finished_project.project_id, bid.bidder, bid.id)).unwrap()); + let bid = test_env + .in_ext(|| Bids::::get((finished_project.project_id, bid.bidder, bid.id)).unwrap()); assert_eq!(schedule.unwrap(), bid.plmc_vesting_info.unwrap().total_amount); } } @@ -2815,11 +2816,15 @@ mod auction_round_success { let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&bid.bidder)); - test_env.in_ext(|| Pallet::::do_vest_plmc_for( - bid.bidder.clone(), - finished_project.project_id, - bid.bidder.clone(), - )).unwrap(); + test_env + .in_ext(|| { + Pallet::::do_vest_plmc_for( + bid.bidder.clone(), + finished_project.project_id, + bid.bidder.clone(), + ) + }) + .unwrap(); let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&bid.bidder)); assert_eq!(locked_amount, post_free_balance - prev_free_balance); @@ -2862,11 +2867,15 @@ mod auction_round_success { let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&bid.bidder)); - test_env.in_ext(|| Pallet::::do_vest_plmc_for( - bid.bidder.clone(), - finished_project.project_id, - bid.bidder.clone(), - )).unwrap(); + test_env + .in_ext(|| { + Pallet::::do_vest_plmc_for( + bid.bidder.clone(), + finished_project.project_id, + bid.bidder.clone(), + ) + }) + .unwrap(); let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&bid.bidder)); assert_eq!(vested_amount, post_free_balance - prev_free_balance); @@ -3122,11 +3131,11 @@ mod auction_round_failure { mod community_round_success { use super::*; + use crate::tests::testing_macros::call_and_is_ok; use frame_support::traits::fungible::Inspect; - use std::assert_matches::assert_matches; use parachains_common::DAYS; use polimec_traits::ReleaseSchedule; - use crate::tests::testing_macros::call_and_is_ok; + use std::assert_matches::assert_matches; pub const HOURS: BlockNumber = 300u64; @@ -4014,7 +4023,9 @@ mod community_round_success { let details = finished_project.get_project_details(); assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); - let contributions = test_env.in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let contributions = test_env.in_ext(|| { + Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>() + }); for contribution in contributions { call_and_is_ok!( test_env, @@ -4033,7 +4044,14 @@ mod community_round_success { ) }); - let contribution = test_env.in_ext(|| Contributions::::get((finished_project.project_id, contribution.contributor, contribution.id)).unwrap()); + let contribution = test_env.in_ext(|| { + Contributions::::get(( + finished_project.project_id, + contribution.contributor, + contribution.id, + )) + .unwrap() + }); assert_eq!(schedule.unwrap(), contribution.plmc_vesting_info.unwrap().total_amount); } } @@ -4062,8 +4080,9 @@ mod community_round_success { let details = finished_project.get_project_details(); assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - let stored_contributions = test_env - .in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_contributions = test_env.in_ext(|| { + Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>() + }); test_env.advance_time((10 * DAYS).into()).unwrap(); @@ -4071,15 +4090,21 @@ mod community_round_success { let vesting_info = contribution.plmc_vesting_info.unwrap(); let locked_amount = vesting_info.total_amount; - let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); + let prev_free_balance = + test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); - test_env.in_ext(|| Pallet::::do_vest_plmc_for( - contribution.contributor.clone(), - finished_project.project_id, - contribution.contributor.clone(), - )).unwrap(); + test_env + .in_ext(|| { + Pallet::::do_vest_plmc_for( + contribution.contributor.clone(), + finished_project.project_id, + contribution.contributor.clone(), + ) + }) + .unwrap(); - let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); + let post_free_balance = + test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); assert_eq!(locked_amount, post_free_balance - prev_free_balance); } } @@ -4108,8 +4133,9 @@ mod community_round_success { let details = finished_project.get_project_details(); assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); let vest_start_block = details.funding_end_block.unwrap(); - let stored_contributions = test_env - .in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_contributions = test_env.in_ext(|| { + Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>() + }); for contribution in stored_contributions { let vesting_info = contribution.plmc_vesting_info.unwrap(); @@ -4118,26 +4144,32 @@ mod community_round_success { let blocks_passed = now - vest_start_block; let vested_amount = vesting_info.amount_per_block * blocks_passed as u128; - let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); + let prev_free_balance = + test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); - test_env.in_ext(|| Pallet::::do_vest_plmc_for( - contribution.contributor.clone(), - finished_project.project_id, - contribution.contributor.clone(), - )).unwrap(); + test_env + .in_ext(|| { + Pallet::::do_vest_plmc_for( + contribution.contributor.clone(), + finished_project.project_id, + contribution.contributor.clone(), + ) + }) + .unwrap(); - let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); + let post_free_balance = + test_env.in_ext(|| ::NativeCurrency::balance(&contribution.contributor)); assert_eq!(vested_amount, post_free_balance - prev_free_balance); } } } mod remainder_round_success { + use super::*; + use crate::tests::testing_macros::{call_and_is_ok, extract_from_event}; use frame_support::traits::fungible::Inspect; use parachains_common::DAYS; use polimec_traits::ReleaseSchedule; - use super::*; - use crate::tests::testing_macros::{call_and_is_ok, extract_from_event}; #[test] fn remainder_round_works() { @@ -4738,7 +4770,8 @@ mod remainder_round_success { let auction_locked_plmc = calculate_auction_plmc_spent_after_price_calculation(bids, price); let community_locked_plmc = calculate_contributed_plmc_spent(community_contributions, price); let remainder_locked_plmc = calculate_contributed_plmc_spent(remainder_contributions, price); - let all_plmc_locks = merge_add_mappings_by_user(vec![auction_locked_plmc, community_locked_plmc, remainder_locked_plmc]); + let all_plmc_locks = + merge_add_mappings_by_user(vec![auction_locked_plmc, community_locked_plmc, remainder_locked_plmc]); test_env.advance_time(10u64).unwrap(); let details = finished_project.get_project_details(); @@ -4784,14 +4817,18 @@ mod remainder_round_success { let details = finished_project.get_project_details(); assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); - let contributions = test_env.in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let contributions = test_env.in_ext(|| { + Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>() + }); for contribution in contributions { - let prev_scheduled = test_env.in_ext(|| { - ::Vesting::total_scheduled_amount( - &contribution.contributor, - LockType::Participation(finished_project.project_id), - ) - }).unwrap_or(Zero::zero()); + let prev_scheduled = test_env + .in_ext(|| { + ::Vesting::total_scheduled_amount( + &contribution.contributor, + LockType::Participation(finished_project.project_id), + ) + }) + .unwrap_or(Zero::zero()); call_and_is_ok!( test_env, @@ -4803,16 +4840,25 @@ mod remainder_round_success { ) ); - let post_scheduled = test_env.in_ext(|| { - ::Vesting::total_scheduled_amount( - &contribution.contributor, - LockType::Participation(finished_project.project_id), - ) - }).unwrap(); + let post_scheduled = test_env + .in_ext(|| { + ::Vesting::total_scheduled_amount( + &contribution.contributor, + LockType::Participation(finished_project.project_id), + ) + }) + .unwrap(); let new_scheduled = post_scheduled - prev_scheduled; - let contribution = test_env.in_ext(|| Contributions::::get((finished_project.project_id, contribution.contributor, contribution.id)).unwrap()); + let contribution = test_env.in_ext(|| { + Contributions::::get(( + finished_project.project_id, + contribution.contributor, + contribution.id, + )) + .unwrap() + }); assert_eq!(new_scheduled, contribution.plmc_vesting_info.unwrap().total_amount); } } @@ -4841,28 +4887,38 @@ mod remainder_round_success { let details = finished_project.get_project_details(); assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - let stored_bids = test_env.in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); - let stored_contributions = test_env - .in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_bids = test_env + .in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_contributions = test_env.in_ext(|| { + Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>() + }); - let bid_plmc_balances = stored_bids.into_iter().map(|b| (b.bidder, b.plmc_vesting_info.unwrap().total_amount)).collect::>(); - let contributed_plmc_balances = stored_contributions.into_iter().map(|c| (c.contributor, c.plmc_vesting_info.unwrap().total_amount)).collect::>(); + let bid_plmc_balances = + stored_bids.into_iter().map(|b| (b.bidder, b.plmc_vesting_info.unwrap().total_amount)).collect::>(); + let contributed_plmc_balances = stored_contributions + .into_iter() + .map(|c| (c.contributor, c.plmc_vesting_info.unwrap().total_amount)) + .collect::>(); let merged_plmc_balances = generic_map_merge_reduce( vec![contributed_plmc_balances.clone(), bid_plmc_balances.clone()], |(account, amount)| account.clone(), BalanceOf::::zero(), - |(account, amount), total| total + amount + |(account, amount), total| total + amount, ); test_env.advance_time((1 * DAYS + 1u32).into()).unwrap(); for (contributor, plmc_amount) in merged_plmc_balances { let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contributor)); - test_env.in_ext(|| Pallet::::do_vest_plmc_for( - contributor.clone(), - finished_project.project_id, - contributor.clone(), - )).unwrap(); + test_env + .in_ext(|| { + Pallet::::do_vest_plmc_for( + contributor.clone(), + finished_project.project_id, + contributor.clone(), + ) + }) + .unwrap(); let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contributor)); assert_eq!(plmc_amount, post_free_balance - prev_free_balance); @@ -4894,31 +4950,39 @@ mod remainder_round_success { assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); let vest_start_block = details.funding_end_block.unwrap(); - let stored_bids = test_env.in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); - let stored_contributions = test_env - .in_ext(|| Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_bids = test_env + .in_ext(|| Bids::::iter_prefix_values((finished_project.project_id,)).collect::>()); + let stored_contributions = test_env.in_ext(|| { + Contributions::::iter_prefix_values((finished_project.project_id,)).collect::>() + }); let now = test_env.current_block(); let blocks_passed = now - vest_start_block; - let bid_plmc_balances = stored_bids.into_iter().map(|b| (b.bidder, b.plmc_vesting_info.unwrap().amount_per_block * blocks_passed as u128)).collect::>(); - let contributed_plmc_balances = stored_contributions.into_iter().map(|c| (c.contributor, c.plmc_vesting_info.unwrap().amount_per_block * blocks_passed as u128)).collect::>(); + let bid_plmc_balances = stored_bids + .into_iter() + .map(|b| (b.bidder, b.plmc_vesting_info.unwrap().amount_per_block * blocks_passed as u128)) + .collect::>(); + let contributed_plmc_balances = stored_contributions + .into_iter() + .map(|c| (c.contributor, c.plmc_vesting_info.unwrap().amount_per_block * blocks_passed as u128)) + .collect::>(); let merged_plmc_balances = generic_map_merge_reduce( vec![contributed_plmc_balances.clone(), bid_plmc_balances.clone()], |(account, amount)| account.clone(), BalanceOf::::zero(), - |(account, amount), total| total + amount + |(account, amount), total| total + amount, ); for (contributor, amount) in merged_plmc_balances { let prev_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contributor)); - test_env.in_ext(|| Pallet::::do_vest_plmc_for( - contributor, - finished_project.project_id, - contributor, - )).unwrap(); + test_env + .in_ext(|| { + Pallet::::do_vest_plmc_for(contributor, finished_project.project_id, contributor) + }) + .unwrap(); let post_free_balance = test_env.in_ext(|| ::NativeCurrency::balance(&contributor)); assert_eq!(amount, post_free_balance - prev_free_balance); From 6a53faf7bd4243537d13478725738a8454ae4f57 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 10 Aug 2023 13:38:56 +0200 Subject: [PATCH 12/14] fix(229): unsuccessful bids don't get vest schedules --- pallets/funding/src/functions.rs | 9 ++-- pallets/funding/src/impls.rs | 16 ++++++- pallets/funding/src/tests.rs | 74 ++++++++++++++++++++++++++++++++ pallets/funding/src/types.rs | 6 --- 4 files changed, 94 insertions(+), 11 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 1dcc12b41..a2ff23ecb 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -27,7 +27,7 @@ use frame_support::{ ensure, pallet_prelude::DispatchError, traits::{ - fungible::{InspectHold, MutateHold as FungibleMutateHold}, + fungible::MutateHold as FungibleMutateHold, fungibles::{metadata::Mutate as MetadataMutate, Create, Inspect, Mutate as FungiblesMutate}, tokens::{Fortitude, Precision, Preservation, Restriction}, Get, @@ -127,6 +127,7 @@ impl Pallet { Ok(()) } + //noinspection ALL /// Called by user extrinsic /// Starts the evaluation round of a project. It needs to be called by the project issuer. /// @@ -1365,7 +1366,9 @@ impl Pallet { // * Validity checks * ensure!( - matches!(bid.plmc_vesting_info, None) && project_details.status == ProjectStatus::FundingSuccessful, + matches!(bid.plmc_vesting_info, None) && + project_details.status == ProjectStatus::FundingSuccessful && + matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)), Error::::NotAllowed ); @@ -1576,7 +1579,7 @@ impl Pallet { _multiplier: MultiplierOf, bonded_amount: BalanceOf, ) -> Result>, DispatchError> { - // TODO: lock_time should depend on `_multiplier` and `_caller` credential + // TODO: duration should depend on `_multiplier` and `_caller` credential let duration: u32 = 1u32 * parachains_common::DAYS; let amount_per_block = bonded_amount.checked_div(&duration.into()).ok_or(Error::::BadMath)?; diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 33d972c34..c61ceb9d7 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -50,7 +50,10 @@ impl DoRemainingOperation for CleanerState { }, CleanerState::EvaluationUnbonding(remaining, PhantomData) => if *remaining == 0 { - *self = CleanerState::StartBidderVestingSchedule(remaining_bids::(project_id), PhantomData); + *self = CleanerState::StartBidderVestingSchedule( + remaining_successful_bids::(project_id), + PhantomData, + ); Ok(Weight::zero()) } else { let (consumed_weight, remaining_evaluations) = unbond_one_evaluation::(project_id); @@ -254,6 +257,12 @@ fn remaining_bids(project_id: T::ProjectIdentifier) -> u64 { Bids::::iter_prefix_values((project_id,)).count() as u64 } +fn remaining_successful_bids(project_id: T::ProjectIdentifier) -> u64 { + Bids::::iter_prefix_values((project_id,)) + .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) + .count() as u64 +} + fn remaining_contributions_to_release_funds(project_id: T::ProjectIdentifier) -> u64 { Contributions::::iter_prefix_values((project_id,)).filter(|contribution| !contribution.funds_released).count() as u64 @@ -478,7 +487,10 @@ fn unbond_one_contribution(project_id: T::ProjectIdentifier) -> (Weig fn start_one_bid_vesting_schedule(project_id: T::ProjectIdentifier) -> (Weight, u64) { let project_bids = Bids::::iter_prefix_values((project_id,)); - let mut unscheduled_bids = project_bids.filter(|bid| matches!(bid.plmc_vesting_info, None)); + let mut unscheduled_bids = project_bids.filter(|bid| { + matches!(bid.plmc_vesting_info, None) && + matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)) + }); if let Some(bid) = unscheduled_bids.next() { match Pallet::::do_start_bid_vesting_schedule_for( diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 7667c544f..9febe7a8a 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -2881,6 +2881,80 @@ mod auction_round_success { assert_eq!(vested_amount, post_free_balance - prev_free_balance); } } + + #[test] + pub fn unsuccessful_bids_dont_get_vest_schedule() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let mut bids = default_bids(); + + let available_tokens = + project.total_allocation_size.saturating_sub(bids.iter().fold(0, |acc, bid| acc + bid.amount)); + + let median_price = bids[bids.len().div(2)].price; + let accepted_bid = + vec![TestBid::new(BIDDER_4, available_tokens, median_price, None, AcceptedFundingAsset::USDT)]; + let rejected_bid = + vec![TestBid::new(BIDDER_5, 50_000 * ASSET_UNIT, median_price, None, AcceptedFundingAsset::USDT)]; + bids.extend(accepted_bid.clone()); + bids.extend(rejected_bid.clone()); + + let community_contributions = default_community_buys(); + + let auctioning_project = AuctioningProject::new_with(&test_env, project, issuer, evaluations); + let mut bidders_plmc = calculate_auction_plmc_spent(bids.clone()); + bidders_plmc.iter_mut().for_each(|(acc, amount)| *amount += get_ed()); + test_env.mint_plmc_to(bidders_plmc.clone()); + + let bidders_funding_assets = calculate_auction_funding_asset_spent(bids.clone()); + test_env.mint_statemint_asset_to(bidders_funding_assets.clone()); + + auctioning_project.bid_for_users(bids).unwrap(); + + let community_funding_project = auctioning_project.start_community_funding(); + let final_price = community_funding_project.get_project_details().weighted_average_price.unwrap(); + let mut contributors_plmc = calculate_contributed_plmc_spent(community_contributions.clone(), final_price); + contributors_plmc.iter_mut().for_each(|(acc, amount)| *amount += get_ed()); + test_env.mint_plmc_to(contributors_plmc.clone()); + + let contributors_funding_assets = + calculate_contributed_funding_asset_spent(community_contributions.clone(), final_price); + test_env.mint_statemint_asset_to(contributors_funding_assets.clone()); + + community_funding_project.buy_for_retail_users(community_contributions).unwrap(); + let finished_project = community_funding_project.finish_funding(); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let plmc_locked_for_accepted_bid = + calculate_auction_plmc_spent_after_price_calculation(accepted_bid, final_price); + let plmc_locked_for_rejected_bid = + calculate_auction_plmc_spent_after_price_calculation(rejected_bid, final_price); + + let (accepted_user, accepted_plmc_amount) = plmc_locked_for_accepted_bid[0]; + let schedule = test_env.in_ext(|| { + ::Vesting::total_scheduled_amount( + &accepted_user, + LockType::Participation(finished_project.project_id), + ) + }); + assert_eq!(schedule.unwrap(), accepted_plmc_amount); + + let (rejected_user, _rejected_plmc_amount) = plmc_locked_for_rejected_bid[0]; + let schedule_exists = test_env + .in_ext(|| { + ::Vesting::total_scheduled_amount( + &rejected_user, + LockType::Participation(finished_project.project_id), + ) + }) + .is_some(); + assert!(!schedule_exists); + } } mod auction_round_failure { diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 4b94efab8..861021772 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -61,12 +61,6 @@ pub mod config_types { Participation(ProjectId), } - #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo, Ord, PartialOrd)] - pub enum ParticipationType { - Bid(StorageItemId), - Contribution(StorageItemId), - } - pub struct ConstPriceProvider(PhantomData<(AssetId, Price, Mapping)>); impl>> ProvideStatemintPrice for ConstPriceProvider From 27ab364c4fd2d2046aa12a5d350e5dc7cb8295c2 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 10 Aug 2023 13:44:43 +0200 Subject: [PATCH 13/14] chore(229): moved all imports in test.rs to top level --- pallets/funding/src/tests.rs | 44 ++++++++++-------------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 9febe7a8a..72b8099c8 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -21,27 +21,33 @@ use super::*; use crate as pallet_funding; use crate::{ mock::{FundingModule, *}, - traits::ProvideStatemintPrice, + tests::testing_macros::{call_and_is_ok, extract_from_event, assert_close_enough}, + traits::{BondingRequirementCalculation, ProvideStatemintPrice}, CurrencyMetadata, Error, ParticipantsSize, ProjectMetadata, TicketSize, + UpdateType::{CommunityFundingStart, RemainderFundingStart}, }; use defaults::*; use frame_support::{ assert_noop, assert_ok, traits::{ - fungible::{InspectHold as FungibleInspectHold, Mutate as FungibleMutate}, - fungibles::Mutate as FungiblesMutate, + fungible::{Inspect as FungibleInspect, InspectHold as FungibleInspectHold, Mutate as FungibleMutate}, + fungibles::{ + metadata::Inspect as MetadataInspect, roles::Inspect as RolesInspect, Inspect as FungiblesInspect, + Mutate as FungiblesMutate, + }, tokens::Balance as BalanceT, OnFinalize, OnIdle, OnInitialize, }, weights::Weight, }; use helper_functions::*; - -use crate::traits::BondingRequirementCalculation; +use parachains_common::DAYS; +use polimec_traits::ReleaseSchedule; use sp_arithmetic::{traits::Zero, Percent, Perquintill}; +use sp_core::H256; use sp_runtime::{DispatchError, Either}; use sp_std::marker::PhantomData; -use std::{cell::RefCell, iter::zip}; +use std::{assert_matches::assert_matches, cell::RefCell, collections::BTreeMap, iter::zip, ops::Div}; type ProjectIdOf = ::ProjectIdentifier; type UserToPLMCBalance = Vec<(AccountId, BalanceOf)>; @@ -1260,12 +1266,6 @@ mod defaults { pub mod helper_functions { use super::*; - use frame_support::traits::fungibles::{ - metadata::Inspect as MetadataInspect, roles::Inspect as RolesInspect, Inspect, - }; - use sp_arithmetic::{traits::Zero, Percent}; - use sp_core::H256; - use std::collections::BTreeMap; pub fn get_ed() -> BalanceOf { ::ExistentialDeposit::get() @@ -1790,8 +1790,6 @@ mod creation_round_failure { mod evaluation_round_success { use super::*; - use sp_arithmetic::Perquintill; - use testing_macros::assert_close_enough; #[test] fn evaluation_round_completed() { @@ -2047,11 +2045,6 @@ mod evaluation_round_failure { mod auction_round_success { use super::*; - use crate::tests::testing_macros::call_and_is_ok; - use frame_support::traits::fungible::Inspect; - use parachains_common::DAYS; - use polimec_traits::ReleaseSchedule; - use std::ops::Div; #[test] fn auction_round_completed() { @@ -3205,11 +3198,6 @@ mod auction_round_failure { mod community_round_success { use super::*; - use crate::tests::testing_macros::call_and_is_ok; - use frame_support::traits::fungible::Inspect; - use parachains_common::DAYS; - use polimec_traits::ReleaseSchedule; - use std::assert_matches::assert_matches; pub const HOURS: BlockNumber = 300u64; @@ -4240,10 +4228,6 @@ mod community_round_success { mod remainder_round_success { use super::*; - use crate::tests::testing_macros::{call_and_is_ok, extract_from_event}; - use frame_support::traits::fungible::Inspect; - use parachains_common::DAYS; - use polimec_traits::ReleaseSchedule; #[test] fn remainder_round_works() { @@ -5066,9 +5050,6 @@ mod remainder_round_success { mod funding_end { use super::*; - use sp_arithmetic::{Percent, Perquintill}; - use std::assert_matches::assert_matches; - use testing_macros::call_and_is_ok; #[test] fn automatic_fail_less_eq_33_percent() { @@ -5650,7 +5631,6 @@ mod test_helper_functions { mod misc_features { use super::*; - use crate::UpdateType::{CommunityFundingStart, RemainderFundingStart}; #[test] fn remove_from_update_store_works() { From ff138884ee07ce0d62ae9cbc8d81dec076435564 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 10 Aug 2023 14:13:28 +0200 Subject: [PATCH 14/14] chore(229): fmt --- pallets/funding/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 72b8099c8..b276caa41 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_funding; use crate::{ mock::{FundingModule, *}, - tests::testing_macros::{call_and_is_ok, extract_from_event, assert_close_enough}, + tests::testing_macros::{assert_close_enough, call_and_is_ok, extract_from_event}, traits::{BondingRequirementCalculation, ProvideStatemintPrice}, CurrencyMetadata, Error, ParticipantsSize, ProjectMetadata, TicketSize, UpdateType::{CommunityFundingStart, RemainderFundingStart},