From f0500c4ad77559f8b98f2e8c1a7cc52cab3c9e14 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Tue, 9 Jul 2024 12:45:57 +0200 Subject: [PATCH 1/7] :recycle: update transition logic --- pallets/funding/README.md | 2 - .../funding/src/functions/1_application.rs | 6 +- pallets/funding/src/functions/2_evaluation.rs | 128 ++++---------- pallets/funding/src/functions/3_auction.rs | 165 ++++-------------- .../funding/src/functions/4_contribution.rs | 101 +++-------- .../funding/src/functions/5_funding_end.rs | 47 +---- pallets/funding/src/functions/misc.rs | 44 +++-- .../src/instantiator/chain_interactions.rs | 17 +- pallets/funding/src/lib.rs | 148 +++------------- pallets/funding/src/mock.rs | 2 - pallets/funding/src/tests/2_evaluation.rs | 15 +- pallets/funding/src/types.rs | 41 ++--- pallets/sandbox/src/mock.rs | 1 - runtimes/polimec/src/benchmarks/mod.rs | 30 ++-- runtimes/polimec/src/lib.rs | 4 +- 15 files changed, 188 insertions(+), 563 deletions(-) diff --git a/pallets/funding/README.md b/pallets/funding/README.md index 4c9be08cb..69ffc899a 100644 --- a/pallets/funding/README.md +++ b/pallets/funding/README.md @@ -89,8 +89,6 @@ on the KILT parachain, by a KYC/AML provider. - `Projects`: Map of the assigned id, to the main information of a project. - `ProjectsInfo`: Map of a project id, to some additional information required for ensuring correctness of the protocol. -- `ProjectsToUpdate`: Map of a block number, to a vector of project ids. Used to - keep track of projects that need to be updated in on_initialize. - `AuctionsInfo`: Double map linking a project-user to the bids they made. - `EvaluationBonds`: Double map linking a project-user to the PLMC they bonded in the evaluation round. diff --git a/pallets/funding/src/functions/1_application.rs b/pallets/funding/src/functions/1_application.rs index f40fbb1a6..d38035fca 100644 --- a/pallets/funding/src/functions/1_application.rs +++ b/pallets/funding/src/functions/1_application.rs @@ -23,9 +23,6 @@ impl Pallet { } let total_allocation_size = project_metadata.total_allocation_size; - // * Calculate new variables * - let now = >::block_number(); - let fundraising_target = project_metadata.minimum_price.checked_mul_int(total_allocation_size).ok_or(Error::::BadMath)?; @@ -36,7 +33,8 @@ impl Pallet { weighted_average_price: None, fundraising_target_usd: fundraising_target, status: ProjectStatus::Application, - phase_transition_points: PhaseTransitionPoints::new(now), + round_duration: BlockNumberPair::new(None, None), + random_end_block: None, remaining_contribution_tokens: project_metadata.total_allocation_size, funding_amount_reached_usd: BalanceOf::::zero(), evaluation_round_info: EvaluationRoundInfoOf:: { diff --git a/pallets/funding/src/functions/2_evaluation.rs b/pallets/funding/src/functions/2_evaluation.rs index 7656703c8..9b7ee3d89 100644 --- a/pallets/funding/src/functions/2_evaluation.rs +++ b/pallets/funding/src/functions/2_evaluation.rs @@ -9,7 +9,6 @@ impl Pallet { /// /// # Storage access /// * [`ProjectsDetails`] - Checking and updating the round status, transition points and freezing the project. - /// * [`ProjectsToUpdate`] - Scheduling the project for automatic transition by on_initialize later on. /// /// # Success path /// The project information is found, its round status was in Application round, and It's not yet frozen. @@ -19,49 +18,28 @@ impl Pallet { /// Users will pond PLMC for this project, and when the time comes, the project will be transitioned /// to the next round by `on_initialize` using [`do_evaluation_end`](Self::do_evaluation_end) #[transactional] - pub fn do_start_evaluation(caller: AccountIdOf, project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn do_start_evaluation(caller: AccountIdOf, project_id: ProjectId) -> DispatchResult { // * Get variables * let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let now = >::block_number(); - + // * Validity checks * ensure!(project_details.issuer_account == caller, Error::::NotIssuer); - ensure!(project_details.status == ProjectStatus::Application, Error::::IncorrectRound); ensure!(!project_details.is_frozen, Error::::ProjectAlreadyFrozen); ensure!(project_metadata.policy_ipfs_cid.is_some(), Error::::CidNotProvided); - - // * Calculate new variables * - let evaluation_end_block = now.saturating_add(T::EvaluationDuration::get()).saturating_sub(One::one()); - project_details.phase_transition_points.application.update(None, Some(now)); - project_details.phase_transition_points.evaluation.update(Some(now), Some(evaluation_end_block)); - project_details.is_frozen = true; - project_details.status = ProjectStatus::EvaluationRound; - + // * Update storage * - ProjectsDetails::::insert(project_id, project_details); - let actual_insertion_attempts = match Self::add_to_update_store( - evaluation_end_block + 1u32.into(), - (&project_id, UpdateType::EvaluationEnd), - ) { - Ok(insertions) => insertions, - Err(insertions) => - return Err(DispatchErrorWithPostInfo { - post_info: PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::start_evaluation(insertions)), - pays_fee: Pays::Yes, - }, - error: Error::::TooManyInsertionAttempts.into(), - }), - }; - - // * Emit events * - Self::deposit_event(Event::ProjectPhaseTransition { project_id, phase: ProjectPhases::Evaluation }); - - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::start_evaluation(actual_insertion_attempts)), - pays_fee: Pays::Yes, - }) + project_details.is_frozen = true; + + // * Transition Round * + Self::transition_project( + project_id, + project_details, + ProjectStatus::Application, + ProjectStatus::EvaluationRound, + T::EvaluationDuration::get(), + false, + ) } /// Called automatically by on_initialize. @@ -93,26 +71,13 @@ impl Pallet { /// * Bonding failed - `on_idle` at some point checks for failed evaluation projects, and /// unbonds the evaluators funds. #[transactional] - pub fn do_evaluation_end(project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn do_evaluation_end(project_id: ProjectId) -> DispatchResult { // * Get variables * let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let now = >::block_number(); - let evaluation_end_block = - project_details.phase_transition_points.evaluation.end().ok_or(Error::::TransitionPointNotSet)?; - let fundraising_target_usd = project_details.fundraising_target_usd; - - // * Validity checks * - ensure!(project_details.status == ProjectStatus::EvaluationRound, Error::::IncorrectRound); - ensure!(now > evaluation_end_block, Error::::TooEarlyForRound); // * Calculate new variables * let usd_total_amount_bonded = project_details.evaluation_round_info.total_bonded_usd; - let evaluation_target_usd = ::EvaluationSuccessThreshold::get() * fundraising_target_usd; - - let auction_initialize_period_start_block = now; - let auction_initialize_period_end_block = auction_initialize_period_start_block - .saturating_add(T::AuctionInitializePeriodDuration::get()) - .saturating_sub(One::one()); + let evaluation_target_usd = ::EvaluationSuccessThreshold::get() * project_details.fundraising_target_usd; // Check which logic path to follow let is_funded = usd_total_amount_bonded >= evaluation_target_usd; @@ -120,56 +85,27 @@ impl Pallet { // * Branch in possible project paths * // Successful path if is_funded { - // * Update storage * - project_details - .phase_transition_points - .auction_initialize_period - .update(Some(auction_initialize_period_start_block), Some(auction_initialize_period_end_block)); - project_details.status = ProjectStatus::AuctionInitializePeriod; - ProjectsDetails::::insert(project_id, project_details); - let insertion_attempts = match Self::add_to_update_store( - auction_initialize_period_end_block + 1u32.into(), - (&project_id, UpdateType::AuctionOpeningStart), - ) { - Ok(insertions) => insertions, - Err(_insertions) => return Err(Error::::TooManyInsertionAttempts.into()), - }; - - // * Emit events * - Self::deposit_event(Event::ProjectPhaseTransition { - project_id, - phase: ProjectPhases::AuctionInitializePeriod, - }); - - return Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::end_evaluation_success(insertion_attempts)), - pays_fee: Pays::Yes, - }); - + return Self::transition_project( + project_id, + project_details, + ProjectStatus::EvaluationRound, + ProjectStatus::AuctionInitializePeriod, + T::AuctionInitializePeriodDuration::get(), + false, + ) // Unsuccessful path } else { - // * Update storage * - - project_details.status = ProjectStatus::FundingFailed; - ProjectsDetails::::insert(project_id, project_details.clone()); let issuer_did = project_details.issuer_did.clone(); DidWithActiveProjects::::set(issuer_did, None); - - let insertion_attempts = - match Self::add_to_update_store(now + One::one(), (&project_id, UpdateType::StartSettlement)) { - Ok(insertions) => insertions, - Err(_insertions) => return Err(Error::::TooManyInsertionAttempts.into()), - }; - - // * Emit events * - Self::deposit_event(Event::ProjectPhaseTransition { + // * Update storage * + return Self::transition_project( project_id, - phase: ProjectPhases::FundingFinalization(ProjectOutcome::EvaluationFailed), - }); - return Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::end_evaluation_failure(insertion_attempts)), - pays_fee: Pays::Yes, - }); + project_details, + ProjectStatus::EvaluationRound, + ProjectStatus::FundingFailed, + One::one(), + false, + ) } } diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index a82da3a43..a1d396b77 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -24,64 +24,20 @@ impl Pallet { /// Later on, `on_initialize` transitions the project into the closing auction round, by calling /// [`do_auction_closing`](Self::do_auction_closing). #[transactional] - pub fn do_start_auction_opening(caller: AccountIdOf, project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn do_start_auction_opening(caller: AccountIdOf, project_id: ProjectId) -> DispatchResult { // * Get variables * - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let now = >::block_number(); - - let auction_initialize_period_start_block = project_details - .phase_transition_points - .auction_initialize_period - .start() - .ok_or(Error::::TransitionPointNotSet)?; - - // * Validity checks * - ensure!( - caller == T::PalletId::get().into_account_truncating() || caller == project_details.issuer_account, - Error::::NotIssuer - ); - - ensure!(now >= auction_initialize_period_start_block, Error::::TooEarlyForRound); - // If the auction is first manually started, the automatic transition fails here. This - // behavior is intended, as it gracefully skips the automatic transition if the - // auction was started manually. - ensure!(project_details.status == ProjectStatus::AuctionInitializePeriod, Error::::IncorrectRound); - - // * Calculate new variables * - let opening_start_block = now; - let opening_end_block = now.saturating_add(T::AuctionOpeningDuration::get()).saturating_sub(One::one()); - - // * Update Storage * - project_details - .phase_transition_points - .auction_opening - .update(Some(opening_start_block), Some(opening_end_block)); - project_details.status = ProjectStatus::AuctionOpening; - ProjectsDetails::::insert(project_id, project_details); - - // Schedule for automatic transition to auction closing round - let insertion_attempts = match Self::add_to_update_store( - opening_end_block + 1u32.into(), - (&project_id, UpdateType::AuctionClosingStart), - ) { - Ok(iterations) => iterations, - Err(insertion_attempts) => - return Err(DispatchErrorWithPostInfo { - post_info: PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::start_auction_manually(insertion_attempts)), - pays_fee: Pays::Yes, - }, - error: Error::::TooManyInsertionAttempts.into(), - }), - }; - - // * Emit events * - Self::deposit_event(Event::ProjectPhaseTransition { project_id, phase: ProjectPhases::AuctionOpening }); + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + // issuer_account can start the auction opening round during the Auction Initialize Period. + let skip_round_end_check = caller == project_details.issuer_account; - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::start_auction_manually(insertion_attempts)), - pays_fee: Pays::Yes, - }) + Self::transition_project( + project_id, + project_details, + ProjectStatus::AuctionInitializePeriod, + ProjectStatus::AuctionOpening, + T::AuctionOpeningDuration::get(), + skip_round_end_check, + ) } /// Called automatically by on_initialize @@ -108,44 +64,17 @@ impl Pallet { /// Later on, `on_initialize` ends the auction closing round and starts the community round, /// by calling [`do_community_funding`](Self::do_start_community_funding). #[transactional] - pub fn do_start_auction_closing(project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn do_start_auction_closing(project_id: ProjectId) -> DispatchResult { // * Get variables * - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let now = >::block_number(); - let opening_end_block = - project_details.phase_transition_points.auction_opening.end().ok_or(Error::::TransitionPointNotSet)?; - - // * Validity checks * - ensure!(now > opening_end_block, Error::::TooEarlyForRound); - ensure!(project_details.status == ProjectStatus::AuctionOpening, Error::::IncorrectRound); - - // * Calculate new variables * - let closing_start_block = now; - let closing_end_block = now.saturating_add(T::AuctionClosingDuration::get()).saturating_sub(One::one()); - - // * Update Storage * - project_details - .phase_transition_points - .auction_closing - .update(Some(closing_start_block), Some(closing_end_block)); - project_details.status = ProjectStatus::AuctionClosing; - ProjectsDetails::::insert(project_id, project_details); - // Schedule for automatic check by on_initialize. Success depending on enough funding reached - let insertion_iterations = match Self::add_to_update_store( - closing_end_block + 1u32.into(), - (&project_id, UpdateType::AuctionClosingEnd), - ) { - Ok(iterations) => iterations, - Err(_iterations) => return Err(Error::::TooManyInsertionAttempts.into()), - }; - - // * Emit events * - Self::deposit_event(Event::::ProjectPhaseTransition { project_id, phase: ProjectPhases::AuctionClosing }); - - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::start_auction_closing_phase(insertion_iterations)), - pays_fee: Pays::Yes, - }) + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + Self::transition_project( + project_id, + project_details, + ProjectStatus::AuctionOpening, + ProjectStatus::AuctionClosing, + T::AuctionClosingDuration::get(), + false, + ) } /// Decides which bids are accepted and which are rejected. @@ -153,56 +82,38 @@ impl Pallet { #[transactional] pub fn do_end_auction_closing(project_id: ProjectId) -> DispatchResultWithPostInfo { // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let now = >::block_number(); - let auction_closing_start_block = - project_details.phase_transition_points.auction_closing.start().ok_or(Error::::TransitionPointNotSet)?; - let auction_closing_end_block = - project_details.phase_transition_points.auction_closing.end().ok_or(Error::::TransitionPointNotSet)?; - - // * Validity checks * - ensure!(now > auction_closing_end_block, Error::::TooEarlyForRound); - ensure!(project_details.status == ProjectStatus::AuctionClosing, Error::::IncorrectRound); - + + let start_block = project_details.round_duration.start().ok_or(Error::::ImpossibleState)?; + let end_block = project_details.round_duration.end().ok_or(Error::::ImpossibleState)?; // * Calculate new variables * - let end_block = Self::select_random_block(auction_closing_start_block, auction_closing_end_block); + let candle_block = Self::select_random_block(start_block, end_block); // * Update Storage * let calculation_result = Self::decide_winning_bids( project_id, - end_block, + candle_block, project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, ); match calculation_result { Err(e) => return Err(DispatchErrorWithPostInfo { post_info: ().into(), error: e }), Ok((accepted_bids_count, rejected_bids_count)) => { - // Get info again after updating it with new price. - let mut project_details = - ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - project_details.phase_transition_points.random_closing_ending = Some(end_block); - project_details.status = ProjectStatus::CalculatingWAP; - ProjectsDetails::::insert(project_id, project_details); - - let insertion_iterations = match Self::add_to_update_store( - now + 1u32.into(), - (&project_id, UpdateType::CommunityFundingStart), - ) { - Ok(iterations) => iterations, - Err(_iterations) => return Err(Error::::TooManyInsertionAttempts.into()), - }; - - // * Emit events * - Self::deposit_event(Event::::ProjectPhaseTransition { + // * Transition Round * + project_details.random_end_block = Some(candle_block); + Self::transition_project( project_id, - phase: ProjectPhases::CalculatingWAP, - }); - + project_details, + ProjectStatus::AuctionClosing, + ProjectStatus::CalculatingWAP, + One::one(), + false, + )?; Ok(PostDispatchInfo { // TODO: make new benchmark actual_weight: Some(WeightInfoOf::::start_community_funding( - insertion_iterations, + 1, accepted_bids_count, rejected_bids_count, )), diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs index 5e2d380b8..1ac9f5172 100644 --- a/pallets/funding/src/functions/4_contribution.rs +++ b/pallets/funding/src/functions/4_contribution.rs @@ -25,54 +25,27 @@ impl Pallet { /// starts the remainder round, where anyone can buy at that price point. #[transactional] pub fn do_start_community_funding(project_id: ProjectId) -> DispatchResultWithPostInfo { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let now = >::block_number(); - let auction_closing_end_block = - project_details.phase_transition_points.auction_closing.end().ok_or(Error::::TransitionPointNotSet)?; - - // * Validity checks * - ensure!(now > auction_closing_end_block, Error::::TooEarlyForRound); - ensure!(project_details.status == ProjectStatus::CalculatingWAP, Error::::IncorrectRound); - - // * Calculate new variables * - let community_start_block = now; - let community_end_block = now.saturating_add(T::CommunityFundingDuration::get()).saturating_sub(One::one()); - - // * Update Storage * + // * Calculate Wap * // let wap_result = Self::calculate_weighted_average_price(project_id); - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; match wap_result { Err(e) => return Err(DispatchErrorWithPostInfo { post_info: ().into(), error: e }), Ok(winning_bids_count) => { - // Get info again after updating it with new price. - project_details - .phase_transition_points - .community - .update(Some(community_start_block), Some(community_end_block)); - project_details.status = ProjectStatus::CommunityRound; - ProjectsDetails::::insert(project_id, project_details); - - let insertion_iterations = match Self::add_to_update_store( - community_end_block + 1u32.into(), - (&project_id, UpdateType::RemainderFundingStart), - ) { - Ok(iterations) => iterations, - Err(_iterations) => return Err(Error::::TooManyInsertionAttempts.into()), - }; - - // * Emit events * - Self::deposit_event(Event::::ProjectPhaseTransition { + Self::transition_project( project_id, - phase: ProjectPhases::CommunityFunding, - }); + project_details, + ProjectStatus::CalculatingWAP, + ProjectStatus::CommunityRound, + T::CommunityFundingDuration::get(), + false, + )?; //TODO: address this let rejected_bids_count = 0; Ok(PostDispatchInfo { actual_weight: Some(WeightInfoOf::::start_community_funding( - insertion_iterations, + 1, winning_bids_count, rejected_bids_count, )), @@ -104,17 +77,9 @@ impl Pallet { /// Later on, `on_initialize` ends the remainder round, and finalizes the project funding, by calling /// [`do_end_funding`](Self::do_end_funding). #[transactional] - pub fn do_start_remainder_funding(project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn do_start_remainder_funding(project_id: ProjectId) -> DispatchResult { // * Get variables * - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let now = >::block_number(); - let community_end_block = - project_details.phase_transition_points.community.end().ok_or(Error::::TransitionPointNotSet)?; - - // * Validity checks * - ensure!(now > community_end_block, Error::::TooEarlyForRound); - ensure!(project_details.status == ProjectStatus::CommunityRound, Error::::IncorrectRound); - + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; // Transition to remainder round was initiated by `do_community_funding`, but the ct // tokens where already sold in the community round. This transition is obsolete. ensure!( @@ -122,31 +87,14 @@ impl Pallet { Error::::RoundTransitionAlreadyHappened ); - // * Calculate new variables * - let remainder_start_block = now; - let remainder_end_block = now.saturating_add(T::RemainderFundingDuration::get()).saturating_sub(One::one()); - - // * Update Storage * - project_details - .phase_transition_points - .remainder - .update(Some(remainder_start_block), Some(remainder_end_block)); - project_details.status = ProjectStatus::RemainderRound; - ProjectsDetails::::insert(project_id, project_details); - // Schedule for automatic transition by `on_initialize` - let insertion_iterations = - match Self::add_to_update_store(remainder_end_block + 1u32.into(), (&project_id, UpdateType::FundingEnd)) { - Ok(iterations) => iterations, - Err(_iterations) => return Err(Error::::TooManyInsertionAttempts.into()), - }; - - // * Emit events * - Self::deposit_event(Event::::ProjectPhaseTransition { project_id, phase: ProjectPhases::RemainderFunding }); - - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::start_remainder_funding(insertion_iterations)), - pays_fee: Pays::Yes, - }) + Self::transition_project( + project_id, + project_details, + ProjectStatus::CommunityRound, + ProjectStatus::RemainderRound, + T::RemainderFundingDuration::get(), + false, + ) } /// Buy tokens in the Community Round at the price set in the Bidding Round @@ -334,15 +282,6 @@ impl Pallet { // If no CTs remain, end the funding phase let mut weight_round_end_flag: Option = None; - if remaining_cts_after_purchase.is_zero() { - let fully_filled_vecs_from_insertion = - match Self::add_to_update_store(now + 1u32.into(), (&project_id, UpdateType::FundingEnd)) { - Ok(iterations) => iterations, - Err(_iterations) => return Err(Error::::TooManyInsertionAttempts.into()), - }; - - weight_round_end_flag = Some(fully_filled_vecs_from_insertion); - } // * Emit events * Self::deposit_event(Event::Contribution { diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/5_funding_end.rs index 941c694cd..2bdd76036 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -36,7 +36,7 @@ impl Pallet { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let remaining_cts = project_details.remaining_contribution_tokens; - let remainder_end_block = project_details.phase_transition_points.remainder.end(); + let remainder_end_block = project_details.round_duration.end(); let now = >::block_number(); let issuer_did = project_details.issuer_did.clone(); @@ -44,8 +44,6 @@ impl Pallet { ensure!( // Can end due to running out of CTs remaining_cts == Zero::zero() || - // or the auction being empty - project_details.status == ProjectStatus::AuctionClosing || // or the last funding round ending matches!(remainder_end_block, Some(end_block) if now > end_block), Error::::TooEarlyForRound @@ -85,34 +83,21 @@ impl Pallet { } else if funding_ratio <= Perquintill::from_percent(75u64) { project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed; project_details.status = ProjectStatus::AwaitingProjectDecision; - let insertion_iterations = match Self::add_to_update_store( - now + T::ManualAcceptanceDuration::get() + 1u32.into(), - (&project_id, UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)), - ) { - Ok(iterations) => iterations, - Err(_iterations) => return Err(Error::::TooManyInsertionAttempts.into()), - }; + ProjectsDetails::::insert(project_id, project_details); Ok(PostDispatchInfo { actual_weight: Some(WeightInfoOf::::end_funding_awaiting_decision_evaluators_slashed( - insertion_iterations, + 1, )), pays_fee: Pays::Yes, }) } else if funding_ratio < Perquintill::from_percent(90u64) { project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Unchanged; project_details.status = ProjectStatus::AwaitingProjectDecision; - let insertion_iterations = match Self::add_to_update_store( - now + T::ManualAcceptanceDuration::get() + 1u32.into(), - (&project_id, UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)), - ) { - Ok(iterations) => iterations, - Err(_iterations) => return Err(Error::::TooManyInsertionAttempts.into()), - }; ProjectsDetails::::insert(project_id, project_details); Ok(PostDispatchInfo { actual_weight: Some(WeightInfoOf::::end_funding_awaiting_decision_evaluators_unchanged( - insertion_iterations, + 1, )), pays_fee: Pays::Yes, }) @@ -120,7 +105,7 @@ impl Pallet { let (reward_info, evaluations_count) = Self::generate_evaluator_rewards_info(project_id)?; project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Rewarded(reward_info); - let insertion_iterations = Self::finalize_funding( + Self::finalize_funding( project_id, project_details, ProjectOutcome::FundingSuccessful, @@ -128,7 +113,7 @@ impl Pallet { )?; return Ok(PostDispatchInfo { actual_weight: Some(WeightInfoOf::::end_funding_automatically_accepted_evaluators_rewarded( - insertion_iterations, + 1, evaluations_count, )), pays_fee: Pays::Yes, @@ -169,23 +154,10 @@ impl Pallet { ensure!(project_details.status == ProjectStatus::AwaitingProjectDecision, Error::::IncorrectRound); // * Update storage * - let insertion_attempts = - match Self::add_to_update_store(now + 1u32.into(), (&project_id, UpdateType::ProjectDecision(decision))) { - Ok(iterations) => iterations, - Err(iterations) => - return Err(DispatchErrorWithPostInfo { - post_info: PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::decide_project_outcome(iterations)), - pays_fee: Pays::Yes, - }, - error: Error::::TooManyInsertionAttempts.into(), - }), - }; - Self::deposit_event(Event::ProjectOutcomeDecided { project_id, decision }); Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::decide_project_outcome(insertion_attempts)), + actual_weight: Some(WeightInfoOf::::decide_project_outcome(1)), pays_fee: Pays::Yes, }) } @@ -204,13 +176,10 @@ impl Pallet { }; ProjectsDetails::::insert(project_id, project_details); - let insertion_iterations = - Self::add_to_update_store(now + settlement_delta, (&project_id, UpdateType::StartSettlement)) - .map_err(|_| Error::::TooManyInsertionAttempts)?; Self::deposit_event(Event::ProjectPhaseTransition { project_id, phase: ProjectPhases::FundingFinalization(outcome), }); - Ok(insertion_iterations) + Ok(1) } } diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index d850fd180..5ec2ba2f2 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -13,21 +13,6 @@ impl Pallet { T::PalletId::get().into_sub_account_truncating(index.saturating_add(One::one())) } - /// Adds a project to the ProjectsToUpdate storage, so it can be updated at some later point in time. - pub fn add_to_update_store(block_number: BlockNumberFor, store: (&ProjectId, UpdateType)) -> Result { - // 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; - for i in 1..T::MaxProjectsToUpdateInsertionAttempts::get() + 1 { - if ProjectsToUpdate::::get(block_number).is_some() { - block_number += 1u32.into(); - } else { - ProjectsToUpdate::::insert(block_number, store); - return Ok(i); - } - } - Err(T::MaxProjectsToUpdateInsertionAttempts::get()) - } pub fn create_bucket_from_metadata(metadata: &ProjectMetadataOf) -> Result, DispatchError> { let auction_allocation_size = metadata.auction_round_allocation_percentage * metadata.total_allocation_size; @@ -630,4 +615,33 @@ impl Pallet { Ok(()) } + + pub(crate) fn transition_project( + project_id: ProjectId, + mut project_details: ProjectDetailsOf, + current_round: ProjectStatus, + next_round: ProjectStatus, + round_duration: BlockNumberFor, + skip_end_check: bool, + ) -> DispatchResult { + /* Verify */ + let now = >::block_number(); + ensure!(project_details.round_duration.ended(now) || skip_end_check, Error::::TooEarlyForRound); + ensure!(project_details.status == current_round, Error::::IncorrectRound); + + + let round_end = now.saturating_add(round_duration).saturating_sub(One::one()); + project_details.round_duration.update(Some(now), Some(round_end)); + project_details.status = next_round; + + // * Update storage * + ProjectsDetails::::insert(project_id, project_details); + + // TODO: FIX event transmition by either doing it outside function or map ProjectStatus + // -> ProjectPhase + // * Emit events * + // Self::deposit_event(Event::ProjectPhaseTransition { project_id, phase: next_round }); + + Ok(()) + } } diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 610506bee..3c8eb1107 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -300,10 +300,8 @@ impl< is_frozen: false, weighted_average_price: None, status: ProjectStatus::Application, - phase_transition_points: PhaseTransitionPoints { - application: BlockNumberPair { start: Some(creation_start_block), end: None }, - ..Default::default() - }, + round_duration: BlockNumberPair::new(None, None), + random_end_block: None, fundraising_target_usd: expected_metadata .minimum_price .checked_mul_int(expected_metadata.total_allocation_size) @@ -388,15 +386,8 @@ impl< } pub fn get_update_block(&mut self, project_id: ProjectId, update_type: &UpdateType) -> Option> { - self.execute(|| { - ProjectsToUpdate::::iter().find_map(|(block, update_tup)| { - if project_id == update_tup.0 && update_type == &update_tup.1 { - Some(block) - } else { - None - } - }) - }) + Some(BlockNumberFor::::zero()) + // TODO: FIX } pub fn create_new_project( diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 2890d4be1..92149dfa0 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -85,7 +85,6 @@ //! * [`Nonce`]: Increasing counter to be used in random number generation. //! * [`ProjectsMetadata`]: Map of the assigned id, to the main information of a project. //! * [`ProjectsDetails`]: Map of a project id, to some additional information required for ensuring correctness of the protocol. -//! * [`ProjectsToUpdate`]: Map of a block number, to a vector of project ids. Used to keep track of projects that need to be updated in on_initialize. //! * [`Bids`]: Double map linking a project-user to the bids they made. //! * [`Evaluations`]: Double map linking a project-user to the PLMC they bonded in the evaluation round. //! * [`Contributions`]: Double map linking a project-user to the contribution tokens they bought in the Community or Remainder round. @@ -198,7 +197,6 @@ pub mod pallet { use super::*; use crate::traits::{BondingRequirementCalculation, ProvideAssetPrice, VestingDurationCalculation}; use frame_support::{ - dispatch::{GetDispatchInfo, PostDispatchInfo}, pallet_prelude::*, storage::KeyPrefixIterator, traits::{OnFinalize, OnIdle, OnInitialize}, @@ -340,14 +338,6 @@ pub mod pallet { #[pallet::constant] type MaxMessageSizeThresholds: Get<(u32, u32)>; - /// max iterations for trying to insert a project on the projects_to_update storage - #[pallet::constant] - type MaxProjectsToUpdateInsertionAttempts: Get; - - /// How many projects should we update in on_initialize each block. Likely one to reduce complexity - #[pallet::constant] - type MaxProjectsToUpdatePerBlock: Get; - /// Multiplier type that decides how much PLMC needs to be bonded for a token buy/bid type Multiplier: Parameter + BondingRequirementCalculation @@ -484,9 +474,6 @@ pub mod pallet { /// StorageMap containing additional information for the projects, relevant for correctness of the protocol pub type ProjectsDetails = StorageMap<_, Blake2_128Concat, ProjectId, ProjectDetailsOf>; - #[pallet::storage] - /// A map to know in which block to update which active projects using on_initialize. - pub type ProjectsToUpdate = StorageMap<_, Blake2_128Concat, BlockNumberFor, (ProjectId, UpdateType)>; #[pallet::storage] /// Keep track of the PLMC bonds made to each project by each evaluator @@ -747,9 +734,6 @@ pub mod pallet { RoundTransitionAlreadyHappened, /// A project's transition point (block number) was not set. TransitionPointNotSet, - /// Too many insertion attempts were made while inserting a project's round transition - /// in the `ProjectsToUpdate` storage. This should not happen in practice. - TooManyInsertionAttempts, // * Issuer related errors. E.g. the action was not executed by the issuer, or the issuer * /// did not have the correct state to execute an action. @@ -879,12 +863,12 @@ pub mod pallet { /// Starts the evaluation round of a project. It needs to be called by the project issuer. #[pallet::call_index(3)] - #[pallet::weight(WeightInfoOf::::start_evaluation(::MaxProjectsToUpdateInsertionAttempts::get() - 1))] + #[pallet::weight(WeightInfoOf::::start_evaluation(1))] pub fn start_evaluation( origin: OriginFor, jwt: UntrustedToken, project_id: ProjectId, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let (account, _did, investor_type, _cid) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; ensure!(investor_type == InvestorType::Institutional, Error::::WrongInvestorType); @@ -895,12 +879,12 @@ pub mod pallet { /// institutional user can set bids for a token_amount/token_price pair. /// Any bids from this point until the auction_closing starts, will be considered as valid. #[pallet::call_index(4)] - #[pallet::weight(WeightInfoOf::::start_auction_manually(::MaxProjectsToUpdateInsertionAttempts::get() - 1))] + #[pallet::weight(WeightInfoOf::::start_auction_manually(1))] pub fn start_auction( origin: OriginFor, jwt: UntrustedToken, project_id: ProjectId, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let (account, _did, investor_type, _cid) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; ensure!(investor_type == InvestorType::Institutional, Error::::WrongInvestorType); @@ -926,16 +910,16 @@ pub mod pallet { #[pallet::call_index(6)] #[pallet::weight(WeightInfoOf::::end_evaluation_success( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ))] - pub fn root_do_evaluation_end(origin: OriginFor, project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn root_do_evaluation_end(origin: OriginFor, project_id: ProjectId) -> DispatchResult { ensure_root(origin)?; Self::do_evaluation_end(project_id) } #[pallet::call_index(7)] - #[pallet::weight(WeightInfoOf::::start_auction_manually(::MaxProjectsToUpdateInsertionAttempts::get() - 1))] - pub fn root_do_auction_opening(origin: OriginFor, project_id: ProjectId) -> DispatchResultWithPostInfo { + #[pallet::weight(WeightInfoOf::::start_auction_manually(1))] + pub fn root_do_auction_opening(origin: OriginFor, project_id: ProjectId) -> DispatchResult { ensure_root(origin)?; Self::do_start_auction_opening(T::PalletId::get().into_account_truncating(), project_id) } @@ -966,29 +950,29 @@ pub mod pallet { #[pallet::call_index(9)] #[pallet::weight(WeightInfoOf::::start_auction_closing_phase( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ))] pub fn root_do_start_auction_closing( origin: OriginFor, project_id: ProjectId, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { ensure_root(origin)?; Self::do_start_auction_closing(project_id) } #[pallet::call_index(10)] #[pallet::weight(WeightInfoOf::::end_auction_closing( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ::MaxBidsPerProject::get() / 2, ::MaxBidsPerProject::get() / 2, ) .max(WeightInfoOf::::end_auction_closing( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ::MaxBidsPerProject::get(), 0u32, )) .max(WeightInfoOf::::end_auction_closing( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, 0u32, ::MaxBidsPerProject::get(), )))] @@ -999,17 +983,17 @@ pub mod pallet { #[pallet::call_index(11)] #[pallet::weight(WeightInfoOf::::start_community_funding( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ::MaxBidsPerProject::get() / 2, ::MaxBidsPerProject::get() / 2, ) .max(WeightInfoOf::::start_community_funding( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ::MaxBidsPerProject::get(), 0u32, )) .max(WeightInfoOf::::start_community_funding( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, 0u32, ::MaxBidsPerProject::get(), )))] @@ -1026,7 +1010,7 @@ pub mod pallet { // Last contribution possible before having to remove an old lower one ::MaxContributionsPerUser::get() -1, // Since we didn't remove any previous lower contribution, we can buy all remaining CTs and try to move to the next phase - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 0, )) )] pub fn community_contribute( @@ -1054,9 +1038,9 @@ pub mod pallet { #[pallet::call_index(13)] #[pallet::weight(WeightInfoOf::::start_remainder_funding( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ))] - pub fn root_do_remainder_funding(origin: OriginFor, project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn root_do_remainder_funding(origin: OriginFor, project_id: ProjectId) -> DispatchResult { ensure_root(origin)?; Self::do_start_remainder_funding(project_id) } @@ -1069,7 +1053,7 @@ pub mod pallet { // Last contribution possible before having to remove an old lower one ::MaxContributionsPerUser::get() -1, // Since we didn't remove any previous lower contribution, we can buy all remaining CTs and try to move to the next phase - ::MaxProjectsToUpdateInsertionAttempts::get() - 1 + 1 )) )] pub fn remaining_contribute( @@ -1097,16 +1081,16 @@ pub mod pallet { #[pallet::call_index(15)] #[pallet::weight(WeightInfoOf::::end_funding_automatically_rejected_evaluators_slashed( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ) .max(WeightInfoOf::::end_funding_awaiting_decision_evaluators_slashed( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, )) .max(WeightInfoOf::::end_funding_awaiting_decision_evaluators_unchanged( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, )) .max(WeightInfoOf::::end_funding_automatically_accepted_evaluators_rewarded( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1, + 1, ::MaxEvaluationsPerProject::get(), )))] pub fn root_do_end_funding(origin: OriginFor, project_id: ProjectId) -> DispatchResultWithPostInfo { @@ -1116,7 +1100,7 @@ pub mod pallet { #[pallet::call_index(16)] #[pallet::weight(WeightInfoOf::::decide_project_outcome( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1 + 1 ))] pub fn decide_project_outcome( origin: OriginFor, @@ -1349,88 +1333,6 @@ pub mod pallet { }, } } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(now: BlockNumberFor) -> Weight { - // Get the projects that need to be updated on this block and update them - let mut used_weight = Weight::from_parts(0, 0); - if let Some((project_id, update_type)) = ProjectsToUpdate::::take(now) { - match update_type { - // EvaluationRound -> AuctionInitializePeriod | ProjectFailed - UpdateType::EvaluationEnd => { - let call = Self::do_evaluation_end(project_id); - let fallback_weight = - Call::::root_do_evaluation_end { project_id }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - - // AuctionInitializePeriod -> AuctionOpening - // Only if it wasn't first handled by user extrinsic - UpdateType::AuctionOpeningStart => { - let call = - Self::do_start_auction_opening(T::PalletId::get().into_account_truncating(), project_id); - let fallback_weight = - Call::::root_do_auction_opening { project_id }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - - // AuctionOpening -> AuctionClosing - UpdateType::AuctionClosingStart => { - let call = Self::do_start_auction_closing(project_id); - let fallback_weight = - Call::::root_do_start_auction_closing { project_id }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - - UpdateType::AuctionClosingEnd => { - let call = Self::do_end_auction_closing(project_id); - let fallback_weight = - Call::::root_do_end_auction_closing { project_id }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - - // AuctionClosing -> CommunityRound - UpdateType::CommunityFundingStart => { - let call = Self::do_start_community_funding(project_id); - let fallback_weight = - Call::::root_do_community_funding { project_id }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - - // CommunityRound -> RemainderRound - UpdateType::RemainderFundingStart => { - let call = Self::do_start_remainder_funding(project_id); - let fallback_weight = - Call::::root_do_remainder_funding { project_id }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - - // CommunityRound || RemainderRound -> FundingEnded - UpdateType::FundingEnd => { - let call = Self::do_end_funding(project_id); - let fallback_weight = Call::::root_do_end_funding { project_id }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - - UpdateType::ProjectDecision(decision) => { - let call = Self::do_project_decision(project_id, decision); - let fallback_weight = - Call::::root_do_project_decision { project_id, decision }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - - UpdateType::StartSettlement => { - let call = Self::do_start_settlement(project_id); - let fallback_weight = - Call::::root_do_start_settlement { project_id }.get_dispatch_info().weight; - update_weight(&mut used_weight, call, fallback_weight); - }, - } - } - used_weight - } - } } pub mod xcm_executor_impl { diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 076ed7653..c7afb17ad 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -422,8 +422,6 @@ impl Config for TestRuntime { type MaxEvaluationsPerProject = ConstU32<512>; type MaxEvaluationsPerUser = ConstU32<4>; type MaxMessageSizeThresholds = MaxMessageSizeThresholds; - type MaxProjectsToUpdateInsertionAttempts = ConstU32<100>; - type MaxProjectsToUpdatePerBlock = ConstU32<1>; type MinUsdPerEvaluation = MinUsdPerEvaluation; type Multiplier = Multiplier; type NativeCurrency = Balances; diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 5c194f0c8..8322f349a 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -301,19 +301,8 @@ mod start_evaluation_extrinsic { is_frozen: true, weighted_average_price: None, status: ProjectStatus::EvaluationRound, - phase_transition_points: PhaseTransitionPoints { - application: BlockNumberPair { start: Some(1u64), end: Some(1u64) }, - evaluation: BlockNumberPair { - start: Some(1u64), - end: Some(::EvaluationDuration::get()), - }, - auction_initialize_period: BlockNumberPair { start: None, end: None }, - auction_opening: BlockNumberPair { start: None, end: None }, - random_closing_ending: None, - auction_closing: BlockNumberPair { start: None, end: None }, - community: BlockNumberPair { start: None, end: None }, - remainder: BlockNumberPair { start: None, end: None }, - }, + round_duration: BlockNumberPair::new(None, None), + random_end_block: None, fundraising_target_usd: project_metadata .minimum_price .saturating_mul_int(project_metadata.total_allocation_size), diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 8554e5c3c..2fa50d379 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -328,7 +328,9 @@ pub mod storage_types { /// The current status of the project pub status: ProjectStatus, /// When the different project phases start and end - pub phase_transition_points: PhaseTransitionPoints, + pub round_duration: BlockNumberPair, + /// Random block end for auction round + pub random_end_block: Option, /// Fundraising target amount in USD (6 decimals) pub fundraising_target_usd: Balance, /// The amount of Contribution Tokens that have not yet been sold @@ -688,32 +690,7 @@ pub mod inner_types { FundingFailed, } - #[derive(Default, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] - pub struct PhaseTransitionPoints { - pub application: BlockNumberPair, - pub evaluation: BlockNumberPair, - pub auction_initialize_period: BlockNumberPair, - pub auction_opening: BlockNumberPair, - pub random_closing_ending: Option, - pub auction_closing: BlockNumberPair, - pub community: BlockNumberPair, - pub remainder: BlockNumberPair, - } - - impl PhaseTransitionPoints { - pub const fn new(now: BlockNumber) -> Self { - Self { - application: BlockNumberPair::new(Some(now), None), - evaluation: BlockNumberPair::new(None, None), - auction_initialize_period: BlockNumberPair::new(None, None), - auction_opening: BlockNumberPair::new(None, None), - random_closing_ending: None, - auction_closing: BlockNumberPair::new(None, None), - community: BlockNumberPair::new(None, None), - remainder: BlockNumberPair::new(None, None), - } - } - } + #[derive(Default, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct BlockNumberPair { @@ -721,7 +698,7 @@ pub mod inner_types { pub end: Option, } - impl BlockNumberPair { + impl BlockNumberPair { pub const fn new(start: Option, end: Option) -> Self { Self { start, end } } @@ -734,6 +711,14 @@ pub mod inner_types { self.end } + pub fn started(&self, at: BlockNumber) -> bool { + self.start.map_or(true, |start| start <= at) + } + + pub fn ended(&self, at: BlockNumber) -> bool { + self.end.map_or(true, |end| end <= at) + } + pub fn update(&mut self, start: Option, end: Option) { let new_state = match (start, end) { (Some(start), None) => (Some(start), self.end), diff --git a/pallets/sandbox/src/mock.rs b/pallets/sandbox/src/mock.rs index d9c5b4884..314c59794 100644 --- a/pallets/sandbox/src/mock.rs +++ b/pallets/sandbox/src/mock.rs @@ -180,7 +180,6 @@ // type MaxBidsPerUser = ConstU32<4>; // type MaxContributionsPerUser = ConstU32<4>; // type MaxEvaluationsPerUser = ConstU32<4>; -// type MaxProjectsToUpdatePerBlock = ConstU32<100>; // type Multiplier = (); // type NativeCurrency = Balances; // type PalletId = FundingPalletId; diff --git a/runtimes/polimec/src/benchmarks/mod.rs b/runtimes/polimec/src/benchmarks/mod.rs index 4a417ef8c..a07674817 100644 --- a/runtimes/polimec/src/benchmarks/mod.rs +++ b/runtimes/polimec/src/benchmarks/mod.rs @@ -9,8 +9,6 @@ fn output_max_pallet_funding_values() { use crate::Runtime; - let max_projects_to_update_insertion_attempts: u32 = - ::MaxProjectsToUpdateInsertionAttempts::get(); let max_evaluations_per_user: u32 = ::MaxEvaluationsPerUser::get(); let max_bids_per_user: u32 = ::MaxBidsPerUser::get(); let max_contributions_per_user: u32 = ::MaxContributionsPerUser::get(); @@ -26,11 +24,11 @@ fn output_max_pallet_funding_values() { let edit_project = SubstrateWeight::::edit_project(); dbg!(edit_project); - let start_evaluation = SubstrateWeight::::start_evaluation(max_projects_to_update_insertion_attempts - 1); + let start_evaluation = SubstrateWeight::::start_evaluation(1); dbg!(start_evaluation); let start_auction_manually = - SubstrateWeight::::start_auction_manually(max_projects_to_update_insertion_attempts - 1); + SubstrateWeight::::start_auction_manually(1); dbg!(start_auction_manually); let evaluation = SubstrateWeight::::evaluation(max_evaluations_per_user - 1); @@ -44,12 +42,12 @@ fn output_max_pallet_funding_values() { let contribution_ends_round = SubstrateWeight::::contribution_ends_round( max_contributions_per_user - 1, - max_projects_to_update_insertion_attempts - 1, + 1, ); dbg!(contribution_ends_round); let decide_project_outcome = - SubstrateWeight::::decide_project_outcome(max_projects_to_update_insertion_attempts - 1); + SubstrateWeight::::decide_project_outcome(1); dbg!(decide_project_outcome); let settle_successful_evaluation = SubstrateWeight::::settle_successful_evaluation(); @@ -71,56 +69,56 @@ fn output_max_pallet_funding_values() { dbg!(settle_failed_contribution); let end_evaluation_success = - SubstrateWeight::::end_evaluation_success(max_projects_to_update_insertion_attempts - 1); + SubstrateWeight::::end_evaluation_success(1); dbg!(end_evaluation_success); let end_evaluation_failure = - SubstrateWeight::::end_evaluation_failure(max_projects_to_update_insertion_attempts - 1); + SubstrateWeight::::end_evaluation_failure(1); dbg!(end_evaluation_failure); let start_auction_closing_phase = - SubstrateWeight::::start_auction_closing_phase(max_projects_to_update_insertion_attempts - 1); + SubstrateWeight::::start_auction_closing_phase(1); dbg!(start_auction_closing_phase); let end_auction_closing = SubstrateWeight::::end_auction_closing( - max_projects_to_update_insertion_attempts - 1, + 1, max_bids_per_project, 0, ); dbg!(end_auction_closing); let start_community_funding = SubstrateWeight::::start_community_funding( - max_projects_to_update_insertion_attempts - 1, + 1, max_bids_per_project, 0, ); dbg!(start_community_funding); let start_remainder_funding = - SubstrateWeight::::start_remainder_funding(max_projects_to_update_insertion_attempts - 1); + SubstrateWeight::::start_remainder_funding(1); dbg!(start_remainder_funding); let end_funding_automatically_rejected_evaluators_slashed = SubstrateWeight::::end_funding_automatically_rejected_evaluators_slashed( - max_projects_to_update_insertion_attempts - 1, + 1, ); dbg!(end_funding_automatically_rejected_evaluators_slashed); let end_funding_awaiting_decision_evaluators_slashed = SubstrateWeight::::end_funding_awaiting_decision_evaluators_slashed( - max_projects_to_update_insertion_attempts - 1, + 1, ); dbg!(end_funding_awaiting_decision_evaluators_slashed); let end_funding_awaiting_decision_evaluators_unchanged = SubstrateWeight::::end_funding_awaiting_decision_evaluators_unchanged( - max_projects_to_update_insertion_attempts - 1, + 1, ); dbg!(end_funding_awaiting_decision_evaluators_unchanged); let end_funding_automatically_accepted_evaluators_rewarded = SubstrateWeight::::end_funding_automatically_accepted_evaluators_rewarded( - max_projects_to_update_insertion_attempts - 1, + 1, max_evaluations_per_project, ); dbg!(end_funding_automatically_accepted_evaluators_rewarded); diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 9a1b4ff20..8e6ca7358 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -241,7 +241,7 @@ impl Contains for BaseCallFilter { pallet_funding::Call::remove_project { .. } | pallet_funding::Call::edit_project { .. } | pallet_funding::Call::start_evaluation { .. } | - pallet_funding::Call::root_do_evaluation_end { .. } | + pallet_funding::Call::end_evaluation { .. } | pallet_funding::Call::evaluate { .. } | pallet_funding::Call::start_auction { .. } | pallet_funding::Call::root_do_auction_opening { .. } | @@ -1059,8 +1059,6 @@ impl pallet_funding::Config for Runtime { type MaxEvaluationsPerProject = ConstU32<512>; type MaxEvaluationsPerUser = ConstU32<16>; type MaxMessageSizeThresholds = MaxMessageSizeThresholds; - type MaxProjectsToUpdateInsertionAttempts = ConstU32<100>; - type MaxProjectsToUpdatePerBlock = ConstU32<1>; type MinUsdPerEvaluation = MinUsdPerEvaluation; type Multiplier = pallet_funding::types::Multiplier; type NativeCurrency = Balances; From a0343013972e28336bb6febd1d8dc55d3f211104 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Sat, 13 Jul 2024 13:06:48 +0200 Subject: [PATCH 2/7] :recycle: remove candlestick auction, calculate wap from bucket and settle failed/overpaid bids later --- pallets/funding/src/benchmarking.rs | 4 +- pallets/funding/src/functions/3_auction.rs | 68 +---- .../funding/src/functions/4_contribution.rs | 58 +--- pallets/funding/src/functions/6_settlement.rs | 42 ++- pallets/funding/src/functions/misc.rs | 270 +++--------------- .../src/instantiator/chain_interactions.rs | 13 +- pallets/funding/src/lib.rs | 52 +--- pallets/funding/src/tests/2_evaluation.rs | 5 +- pallets/funding/src/tests/3_auction.rs | 32 +-- pallets/funding/src/tests/4_community.rs | 2 +- pallets/funding/src/tests/5_remainder.rs | 2 +- pallets/funding/src/tests/6_funding_end.rs | 5 +- pallets/funding/src/tests/misc.rs | 20 ++ pallets/funding/src/types.rs | 55 ++-- runtimes/polimec/src/lib.rs | 2 +- 15 files changed, 179 insertions(+), 451 deletions(-) diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 8c50baf3f..cd51f1f82 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -2109,13 +2109,13 @@ mod benchmarks { #[block] { - Pallet::::do_end_auction_closing(project_id).unwrap(); + Pallet::::do_end_auction(project_id).unwrap(); } // * validity checks * // Storage let stored_details = ProjectsDetails::::get(project_id).unwrap(); - assert_eq!(stored_details.status, ProjectStatus::CalculatingWAP); + assert_eq!(stored_details.status, ProjectStatus::CommunityRound); assert!( stored_details.phase_transition_points.random_closing_ending.unwrap() < stored_details.phase_transition_points.auction_closing.end().unwrap() diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index a1d396b77..8eefc3489 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -24,7 +24,7 @@ impl Pallet { /// Later on, `on_initialize` transitions the project into the closing auction round, by calling /// [`do_auction_closing`](Self::do_auction_closing). #[transactional] - pub fn do_start_auction_opening(caller: AccountIdOf, project_id: ProjectId) -> DispatchResult { + pub fn do_start_auction(caller: AccountIdOf, project_id: ProjectId) -> DispatchResult { // * Get variables * let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; // issuer_account can start the auction opening round during the Auction Initialize Period. @@ -34,80 +34,42 @@ impl Pallet { project_id, project_details, ProjectStatus::AuctionInitializePeriod, - ProjectStatus::AuctionOpening, + ProjectStatus::Auction, T::AuctionOpeningDuration::get(), skip_round_end_check, ) } - /// Called automatically by on_initialize - /// Starts the auction closing round for a project. - /// Any bids from this point until the auction closing round ends are not guaranteed. - /// Only bids made before the random ending block between the auction closing start and end will be considered. - /// - /// # Arguments - /// * `project_id` - The project identifier - /// - /// # Storage access - /// * [`ProjectsDetails`] - Get the project information, and check if the project is in the correct - /// round, and the current block after the opening auction end period. - /// Update the project information with the new round status and transition points in case of success. - /// - /// # Success Path - /// The validity checks pass, and the project is transitioned to the auction closing round. - /// The project is scheduled to be transitioned automatically by `on_initialize` at the end of the - /// auction closing round. - /// - /// # Next step - /// Professional and Institutional users set bids for the project using the `bid` extrinsic, - /// but now their bids are not guaranteed. - /// Later on, `on_initialize` ends the auction closing round and starts the community round, - /// by calling [`do_community_funding`](Self::do_start_community_funding). - #[transactional] - pub fn do_start_auction_closing(project_id: ProjectId) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - Self::transition_project( - project_id, - project_details, - ProjectStatus::AuctionOpening, - ProjectStatus::AuctionClosing, - T::AuctionClosingDuration::get(), - false, - ) - } - /// Decides which bids are accepted and which are rejected. /// Deletes and refunds the rejected ones, and prepares the project for the WAP calculation the next block #[transactional] - pub fn do_end_auction_closing(project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn do_end_auction(project_id: ProjectId) -> DispatchResultWithPostInfo { // * Get variables * let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; + let bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; - let start_block = project_details.round_duration.start().ok_or(Error::::ImpossibleState)?; - let end_block = project_details.round_duration.end().ok_or(Error::::ImpossibleState)?; - // * Calculate new variables * - let candle_block = Self::select_random_block(start_block, end_block); + // * Calculate WAP * + let auction_allocation_size = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let weighted_token_price = bucket.calculate_wap(auction_allocation_size); // * Update Storage * let calculation_result = Self::decide_winning_bids( project_id, - candle_block, project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, + weighted_token_price, ); match calculation_result { Err(e) => return Err(DispatchErrorWithPostInfo { post_info: ().into(), error: e }), Ok((accepted_bids_count, rejected_bids_count)) => { // * Transition Round * - project_details.random_end_block = Some(candle_block); Self::transition_project( project_id, project_details, - ProjectStatus::AuctionClosing, - ProjectStatus::CalculatingWAP, - One::one(), + ProjectStatus::Auction, + ProjectStatus::CommunityRound, + T::CommunityFundingDuration::get(), false, )?; Ok(PostDispatchInfo { @@ -149,8 +111,6 @@ impl Pallet { // * Get variables * let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let plmc_usd_price = T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) - .ok_or(Error::::PriceNotFound)?; // Fetch current bucket details and other required info let mut current_bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; @@ -190,7 +150,7 @@ impl Pallet { ensure!(ct_amount > Zero::zero(), Error::::TooLow); ensure!(did != project_details.issuer_did, Error::::ParticipationToOwnProject); ensure!( - matches!(project_details.status, ProjectStatus::AuctionOpening | ProjectStatus::AuctionClosing), + matches!(project_details.status, ProjectStatus::Auction), Error::::IncorrectRound ); ensure!( @@ -231,7 +191,6 @@ impl Pallet { funding_asset, bid_id, now, - plmc_usd_price, did.clone(), metadata_bidder_ticket_size_bounds, existing_bids_amount.saturating_add(perform_bid_calls), @@ -264,7 +223,6 @@ impl Pallet { funding_asset: AcceptedFundingAsset, bid_id: u32, now: BlockNumberFor, - plmc_usd_price: T::Price, did: Did, metadata_ticket_size_bounds: TicketSizeOf, total_bids_by_bidder: u32, @@ -289,7 +247,7 @@ impl Pallet { // * Calculate new variables * let plmc_bond = - Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price).map_err(|_| Error::::BadMath)?; + Self::calculate_plmc_bond(ticket_size, multiplier).map_err(|_| Error::::BadMath)?; let funding_asset_amount_locked = funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs index 1ac9f5172..4b03d2699 100644 --- a/pallets/funding/src/functions/4_contribution.rs +++ b/pallets/funding/src/functions/4_contribution.rs @@ -1,60 +1,6 @@ use super::*; impl Pallet { - /// Called automatically by on_initialize - /// Starts the community round for a project. - /// Retail users now buy tokens instead of bidding on them. The price of the tokens are calculated - /// based on the available bids, using the function [`calculate_weighted_average_price`](Self::calculate_weighted_average_price). - /// - /// # Arguments - /// * `project_id` - The project identifier - /// - /// # Storage access - /// * [`ProjectsDetails`] - Get the project information, and check if the project is in the correct - /// round, and the current block is after the auction closing end period. - /// Update the project information with the new round status and transition points in case of success. - /// - /// # Success Path - /// The validity checks pass, and the project is transitioned to the Community Funding round. - /// The project is scheduled to be transitioned automatically by `on_initialize` at the end of the - /// round. - /// - /// # Next step - /// Retail users buy tokens at the price set on the auction round. - /// Later on, `on_initialize` ends the community round by calling [`do_remainder_funding`](Self::do_start_remainder_funding) and - /// starts the remainder round, where anyone can buy at that price point. - #[transactional] - pub fn do_start_community_funding(project_id: ProjectId) -> DispatchResultWithPostInfo { - // * Calculate Wap * // - let wap_result = Self::calculate_weighted_average_price(project_id); - - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - match wap_result { - Err(e) => return Err(DispatchErrorWithPostInfo { post_info: ().into(), error: e }), - Ok(winning_bids_count) => { - Self::transition_project( - project_id, - project_details, - ProjectStatus::CalculatingWAP, - ProjectStatus::CommunityRound, - T::CommunityFundingDuration::get(), - false, - )?; - - //TODO: address this - let rejected_bids_count = 0; - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::start_community_funding( - 1, - winning_bids_count, - rejected_bids_count, - )), - pays_fee: Pays::Yes, - }) - }, - } - } - /// Called automatically by on_initialize /// Starts the remainder round for a project. /// Anyone can now buy tokens, until they are all sold out, or the time is reached. @@ -199,8 +145,6 @@ impl Pallet { let total_usd_bought_by_did = ContributionBoughtUSD::::get((project_id, did.clone())); let now = >::block_number(); let ct_usd_price = project_details.weighted_average_price.ok_or(Error::::WapNotSet)?; - let plmc_usd_price = T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) - .ok_or(Error::::PriceNotFound)?; let funding_asset_id = funding_asset.to_assethub_id(); let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id); @@ -248,7 +192,7 @@ impl Pallet { Error::::TooHigh ); - let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price)?; + let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier)?; let funding_asset_amount = funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); let asset_id = funding_asset.to_assethub_id(); diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index 750e6ceff..8606b34db 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -4,9 +4,9 @@ use frame_support::{ dispatch::DispatchResult, ensure, traits::{ - fungible::MutateHold as FungibleMutateHold, + fungible::{MutateHold as FungibleMutateHold, Inspect as FungibleInspect}, fungibles::{Inspect, Mutate as FungiblesMutate}, - tokens::{Fortitude, Precision, Preservation, Restriction}, + tokens::{Fortitude, Precision, Preservation, Restriction, Provenance, DepositConsequence}, Get, }, }; @@ -189,15 +189,20 @@ impl Pallet { ); ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::::TooEarlyForRound); + let (refund_plmc, refund_funding_asset) = Self::calculate_refund(&bid)?; + let bidder = bid.bidder; - // Calculate the vesting info and add the release schedule let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; + let new_bond = bid.plmc_bond.saturating_sub(refund_plmc); let vest_info = - Self::calculate_vesting_info(&bidder, bid.multiplier, bid.plmc_bond).map_err(|_| Error::::BadMath)?; + Self::calculate_vesting_info(&bidder, bid.multiplier, new_bond).map_err(|_| Error::::BadMath)?; // If the multiplier is greater than 1, add the release schedule else release the held PLMC bond if bid.multiplier.into() > 1u8 { + if refund_plmc > Zero::zero() { + Self::release_participation_bond(project_id, &bidder, refund_plmc)?; + } T::Vesting::add_release_schedule( &bidder, vest_info.total_amount, @@ -213,11 +218,16 @@ impl Pallet { // Mint the contribution tokens Self::mint_contribution_tokens(project_id, &bidder, bid.final_ct_amount)?; + let new_funding_asset_amount_locked = bid.funding_asset_amount_locked.saturating_sub(refund_funding_asset); + if refund_funding_asset > Zero::zero() { + Self::release_funding_asset(project_id, &bidder, refund_funding_asset , bid.funding_asset)?; + } + // Payout the bid funding asset amount to the project account Self::release_funding_asset( project_id, &project_metadata.funding_destination_account, - bid.funding_asset_amount_locked, + new_funding_asset_amount_locked, bid.funding_asset, )?; @@ -242,10 +252,30 @@ impl Pallet { Ok(()) } + /// Calculate the amount of funds the biider should receive back based on the original bid + /// amount and price compared to the final bid amount and price. + fn calculate_refund(bid: &BidInfoOf) -> Result<(BalanceOf, BalanceOf), DispatchError> { + let new_ticket_size = + bid.final_ct_usd_price.checked_mul_int(bid.final_ct_amount).ok_or(Error::::BadMath)?; + + let new_plmc_bond = Self::calculate_plmc_bond(new_ticket_size, bid.multiplier)?; + let new_funding_asset_amount = Self::calculate_funding_asset_lock(new_ticket_size, bid.funding_asset)?; + let refund_plmc = bid.plmc_bond.saturating_sub(new_plmc_bond); + let refund_funding_asset = bid.funding_asset_amount_locked.saturating_sub(new_funding_asset_amount); + if T::FundingCurrency::can_deposit(bid.funding_asset.to_assethub_id(), &bid.bidder, refund_funding_asset, Provenance::Extant) != DepositConsequence::Success { + refund_funding_asset = Zero::zero(); + } + if T::NativeCurrency::can_deposit(&bid.bidder, refund_plmc, Provenance::Extant) != DepositConsequence::Success { + refund_plmc = Zero::zero(); + } + + Ok((refund_plmc, refund_funding_asset)) + } + pub fn do_settle_failed_bid(bid: BidInfoOf, project_id: ProjectId) -> DispatchResult { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; ensure!( - matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)), + matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)) || bid.status == BidStatus::Rejected, Error::::FundingFailedSettlementNotStarted ); diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 5ec2ba2f2..80e811ce6 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -1,3 +1,5 @@ +use sp_runtime::traits::CheckedAdd; + use super::*; // Helper functions @@ -30,10 +32,26 @@ impl Pallet { pub fn calculate_plmc_bond( ticket_size: BalanceOf, multiplier: MultiplierOf, - plmc_price: PriceOf, ) -> Result, DispatchError> { + let plmc_usd_price = T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) + .ok_or(Error::::PriceNotFound)?; 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()) + plmc_usd_price.reciprocal().ok_or(Error::::BadMath)?.checked_mul_int(usd_bond).ok_or(Error::::BadMath.into()) + } + + pub fn calculate_funding_asset_lock( + ticket_size: BalanceOf, + asset_id: AcceptedFundingAsset, + ) -> Result, DispatchError> { + let asset_id = asset_id.to_assethub_id(); + let asset_decimals = T::FundingCurrency::decimals(asset_id); + let asset_usd_price = + T::PriceProvider::get_decimals_aware_price(asset_id, USD_DECIMALS, asset_decimals) + .ok_or(Error::::PriceNotFound)?; + asset_usd_price + .reciprocal() + .and_then(|recip| recip.checked_mul_int(ticket_size)) + .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, @@ -56,280 +74,66 @@ impl Pallet { pub fn decide_winning_bids( project_id: ProjectId, - end_block: BlockNumberFor, auction_allocation_size: BalanceOf, + wap: PriceOf, ) -> Result<(u32, u32), DispatchError> { // Get all the bids that were made before the end of the closing period. let mut bids = Bids::::iter_prefix_values((project_id,)).collect::>(); // temp variable to store the sum of the bids let mut bid_token_amount_sum = Zero::zero(); - // temp variable to store the total value of the bids (i.e price * amount = Cumulative Ticket Size) - let mut bid_usd_value_sum = BalanceOf::::zero(); - let project_account = Self::fund_account_id(project_id); - - let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let mut highest_accepted_price = project_metadata.minimum_price; - // sort bids by price, and equal prices sorted by id bids.sort_by(|a, b| b.cmp(a)); - // accept only bids that were made before `end_block` i.e end of the the auction candle. let (accepted_bids, rejected_bids): (Vec<_>, Vec<_>) = bids .into_iter() .map(|mut bid| { - if bid.when > end_block { - bid.status = BidStatus::Rejected(RejectionReason::AfterClosingEnd); - return bid; - } let buyable_amount = auction_allocation_size.saturating_sub(bid_token_amount_sum); if buyable_amount.is_zero() { - bid.status = BidStatus::Rejected(RejectionReason::NoTokensLeft); + bid.status = BidStatus::Rejected; } else if bid.original_ct_amount <= buyable_amount { - let ticket_size = bid.original_ct_usd_price.saturating_mul_int(bid.original_ct_amount); + if bid.final_ct_usd_price > wap { + bid.final_ct_usd_price = wap; + } bid_token_amount_sum.saturating_accrue(bid.original_ct_amount); - bid_usd_value_sum.saturating_accrue(ticket_size); bid.final_ct_amount = bid.original_ct_amount; bid.status = BidStatus::Accepted; DidWithWinningBids::::mutate(project_id, bid.did.clone(), |flag| { *flag = true; }); - highest_accepted_price = highest_accepted_price.max(bid.original_ct_usd_price); } else { - let ticket_size = bid.original_ct_usd_price.saturating_mul_int(buyable_amount); - bid_usd_value_sum.saturating_accrue(ticket_size); bid_token_amount_sum.saturating_accrue(buyable_amount); - bid.status = BidStatus::PartiallyAccepted(buyable_amount, RejectionReason::NoTokensLeft); + bid.final_ct_amount = buyable_amount; + bid.status = BidStatus::PartiallyAccepted(buyable_amount); DidWithWinningBids::::mutate(project_id, bid.did.clone(), |flag| { *flag = true; }); - bid.final_ct_amount = buyable_amount; - highest_accepted_price = highest_accepted_price.max(bid.original_ct_usd_price); } Bids::::insert((project_id, &bid.bidder, &bid.id), &bid); bid }) .partition(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))); - // Refund rejected bids. We do it here, so we don't have to calculate all the project - // prices and then fail to refund the bids. - let total_rejected_bids = rejected_bids.len() as u32; - for bid in rejected_bids.into_iter() { - Self::refund_bid(&bid, project_id, &project_account)?; - Bids::::remove((project_id, &bid.bidder, &bid.id)); - } - - ProjectsDetails::::mutate(project_id, |maybe_info| -> DispatchResult { - if let Some(info) = maybe_info { - info.remaining_contribution_tokens.saturating_reduce(bid_token_amount_sum); - if highest_accepted_price > project_metadata.minimum_price { - info.usd_bid_on_oversubscription = Some(bid_usd_value_sum); - } - Ok(()) - } else { - Err(Error::::ProjectDetailsNotFound.into()) + let accepted_bid_len = accepted_bids.len() as u32; + let mut total_auction_allocation_usd: BalanceOf = accepted_bids.into_iter() + .try_fold(Zero::zero(), |acc: BalanceOf, bid: BidInfoOf| { + bid.final_ct_usd_price.checked_mul_int(bid.final_ct_amount).and_then( + |ticket| acc.checked_add(&ticket) + ) } - })?; - - Ok((accepted_bids.len() as u32, total_rejected_bids)) - } - - /// Calculates the price (in USD) of contribution tokens for the Community and Remainder Rounds - pub fn calculate_weighted_average_price(project_id: ProjectId) -> Result { - let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - // Rejected bids were deleted in the previous block. - let accepted_bids = Bids::::iter_prefix_values((project_id,)).collect::>(); - let project_account = Self::fund_account_id(project_id); - let plmc_price = T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) - .ok_or(Error::::PriceNotFound)?; - - // Calculate the weighted price of the token for the next funding rounds, using winning bids. - // for example: if there are 3 winning bids, - // A: 10K tokens @ USD15 per token = 150K USD value - // B: 20K tokens @ USD20 per token = 400K USD value - // C: 20K tokens @ USD10 per token = 200K USD value, - - // then the weight for each bid is: - // A: 150K / (150K + 400K + 200K) = 0.20 - // B: 400K / (150K + 400K + 200K) = 0.533... - // C: 200K / (150K + 400K + 200K) = 0.266... - - // then multiply each weight by the price of the token to get the weighted price per bid - // A: 0.20 * 15 = 3 - // B: 0.533... * 20 = 10.666... - // C: 0.266... * 10 = 2.666... - - // lastly, sum all the weighted prices to get the final weighted price for the next funding round - // 3 + 10.6 + 2.6 = 16.333... - - // After reading from storage all accepted bids when calculating the weighted price of each bid, we store them here - let mut weighted_token_price = if let Some(total_usd_bid) = project_details.usd_bid_on_oversubscription { - let calc_weighted_price_fn = |bid: &BidInfoOf| -> PriceOf { - let ticket_size = bid.original_ct_usd_price.saturating_mul_int(bid.final_ct_amount); - let bid_weight = ::saturating_from_rational(ticket_size, total_usd_bid); - let weighted_price = bid.original_ct_usd_price.saturating_mul(bid_weight); - weighted_price - }; - - accepted_bids - .iter() - .map(calc_weighted_price_fn) - .fold(Zero::zero(), |a: PriceOf, b: PriceOf| a.saturating_add(b)) - } else { - project_metadata.minimum_price - }; - - // We are 99% sure that the price cannot be less than the minimum if some accepted bids have higher price, but rounding - // errors are strange, so we keep this just in case. - if weighted_token_price < project_metadata.minimum_price { - weighted_token_price = project_metadata.minimum_price; - }; - - let mut final_total_funding_reached_by_bids = BalanceOf::::zero(); - - let total_accepted_bids = accepted_bids.len() as u32; - for mut bid in accepted_bids { - if bid.final_ct_usd_price > weighted_token_price || matches!(bid.status, BidStatus::PartiallyAccepted(..)) { - if bid.final_ct_usd_price > weighted_token_price { - bid.final_ct_usd_price = weighted_token_price; - } - - let new_ticket_size = - bid.final_ct_usd_price.checked_mul_int(bid.final_ct_amount).ok_or(Error::::BadMath)?; - - let funding_asset_id = bid.funding_asset.to_assethub_id(); - let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id); - let funding_asset_usd_price = - T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) - .ok_or(Error::::PriceNotFound)?; - - let funding_asset_amount_needed = funding_asset_usd_price - .reciprocal() - .ok_or(Error::::BadMath)? - .checked_mul_int(new_ticket_size) - .ok_or(Error::::BadMath)?; - - let amount_returned = bid.funding_asset_amount_locked.saturating_sub(funding_asset_amount_needed); - let asset_id = bid.funding_asset.to_assethub_id(); - let min_amount = T::FundingCurrency::minimum_balance(asset_id); - // Transfers of less than min_amount return an error - if amount_returned > min_amount { - T::FundingCurrency::transfer( - bid.funding_asset.to_assethub_id(), - &project_account, - &bid.bidder, - amount_returned, - Preservation::Preserve, - )?; - bid.funding_asset_amount_locked = funding_asset_amount_needed; - } - - let usd_bond_needed = bid - .multiplier - .calculate_bonding_requirement::(new_ticket_size) - .map_err(|_| Error::::BadMath)?; - let plmc_bond_needed = plmc_price - .reciprocal() - .ok_or(Error::::BadMath)? - .checked_mul_int(usd_bond_needed) - .ok_or(Error::::BadMath)?; - - let plmc_bond_returned = bid.plmc_bond.saturating_sub(plmc_bond_needed); - // If the free balance of a user is zero and we want to send him less than ED, it will fail. - if plmc_bond_returned > T::ExistentialDeposit::get() { - T::NativeCurrency::release( - &HoldReason::Participation(project_id).into(), - &bid.bidder, - plmc_bond_returned, - Precision::Exact, - )?; - } - - bid.plmc_bond = plmc_bond_needed; - } - let final_ticket_size = - bid.final_ct_usd_price.checked_mul_int(bid.final_ct_amount).ok_or(Error::::BadMath)?; - final_total_funding_reached_by_bids.saturating_accrue(final_ticket_size); - Bids::::insert((project_id, &bid.bidder, &bid.id), &bid); - } + ).ok_or(Error::::BadMath)?; ProjectsDetails::::mutate(project_id, |maybe_info| -> DispatchResult { if let Some(info) = maybe_info { - info.weighted_average_price = Some(weighted_token_price); - info.funding_amount_reached_usd.saturating_accrue(final_total_funding_reached_by_bids); + info.remaining_contribution_tokens.saturating_reduce(bid_token_amount_sum); + info.funding_amount_reached_usd.saturating_accrue(total_auction_allocation_usd); Ok(()) } else { Err(Error::::ProjectDetailsNotFound.into()) } })?; - Ok(total_accepted_bids) - } - - /// Refund a bid because of `reason`. - fn refund_bid( - bid: &BidInfoOf, - project_id: ProjectId, - project_account: &AccountIdOf, - ) -> Result<(), DispatchError> { - T::FundingCurrency::transfer( - bid.funding_asset.to_assethub_id(), - project_account, - &bid.bidder, - bid.funding_asset_amount_locked, - Preservation::Expendable, - )?; - T::NativeCurrency::release( - &HoldReason::Participation(project_id).into(), - &bid.bidder, - bid.plmc_bond, - Precision::Exact, - )?; - - // Refund bid should only be called when the bid is rejected, so this if let should - // always match. - if let BidStatus::Rejected(reason) = bid.status { - Self::deposit_event(Event::BidRefunded { - project_id, - account: bid.bidder.clone(), - bid_id: bid.id, - reason, - plmc_amount: bid.plmc_bond, - funding_asset: bid.funding_asset, - funding_amount: bid.funding_asset_amount_locked, - }); - } - - Ok(()) - } - - pub fn select_random_block( - closing_starting_block: BlockNumberFor, - closing_ending_block: BlockNumberFor, - ) -> BlockNumberFor { - let nonce = Self::get_and_increment_nonce(); - let (random_value, _known_since) = T::Randomness::random(&nonce); - let random_block = >::decode(&mut random_value.as_ref()) - .expect("secure hashes should always be bigger than the block number; qed"); - let block_range = closing_ending_block - closing_starting_block; - - closing_starting_block + (random_block % block_range) + Ok((accepted_bid_len, 0)) } - fn get_and_increment_nonce() -> Vec { - let nonce = Nonce::::get(); - Nonce::::put(nonce.wrapping_add(1)); - nonce.encode() - } - - /// People that contributed to the project during the Funding Round can claim their Contribution Tokens - // This function is kept separate from the `do_claim_contribution_tokens` for easier testing the logic - #[inline(always)] - pub fn calculate_claimable_tokens( - contribution_amount: BalanceOf, - weighted_average_price: BalanceOf, - ) -> FixedU128 { - FixedU128::saturating_from_rational(contribution_amount, weighted_average_price) - } pub fn try_plmc_participation_lock( who: &T::AccountId, diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 3c8eb1107..db4e061bd 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -464,9 +464,9 @@ impl< assert_eq!(self.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); - self.execute(|| crate::Pallet::::do_start_auction_opening(caller, project_id).unwrap()); + self.execute(|| crate::Pallet::::do_start_auction(caller, project_id).unwrap()); - assert_eq!(self.get_project_details(project_id).status, ProjectStatus::AuctionOpening); + assert_eq!(self.get_project_details(project_id).status, ProjectStatus::Auction); Ok(()) } @@ -1197,11 +1197,10 @@ impl< community_contributions, ), ProjectStatus::CommunityRound => - self.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids), - ProjectStatus::AuctionOpening => - self.create_auctioning_project(project_metadata, issuer, None, evaluations), - ProjectStatus::EvaluationRound => self.create_evaluating_project(project_metadata, issuer, None), - ProjectStatus::Application => self.create_new_project(project_metadata, issuer, None), + self.create_community_contributing_project(project_metadata, issuer, evaluations, bids), + ProjectStatus::Auction => self.create_auctioning_project(project_metadata, issuer, evaluations), + ProjectStatus::EvaluationRound => self.create_evaluating_project(project_metadata, issuer), + ProjectStatus::Application => self.create_new_project(project_metadata, issuer), _ => panic!("unsupported project creation in that status"), } } diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 92149dfa0..47ee90763 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -82,7 +82,6 @@ //! ### Storage Items //! * [`NextProjectId`] : Increasing counter to get the next id to assign to a project. //! * [`NextBidId`]: Increasing counter to get the next id to assign to a bid. -//! * [`Nonce`]: Increasing counter to be used in random number generation. //! * [`ProjectsMetadata`]: Map of the assigned id, to the main information of a project. //! * [`ProjectsDetails`]: Map of a project id, to some additional information required for ensuring correctness of the protocol. //! * [`Bids`]: Double map linking a project-user to the bids they made. @@ -356,7 +355,8 @@ pub mod pallet { Balance = BalanceOf, Reason = ::RuntimeHoldReason, > + fungible::BalancedHold, Balance = BalanceOf> - + fungible::Mutate, Balance = BalanceOf>; + + fungible::Mutate, Balance = BalanceOf> + + fungible::Inspect, Balance = BalanceOf>; /// System account for the funding pallet. Used to derive project escrow accounts. #[pallet::constant] @@ -456,12 +456,6 @@ pub mod pallet { #[pallet::storage] pub type EvaluationCounts = StorageMap<_, Blake2_128Concat, ProjectId, u32, ValueQuery>; - #[pallet::storage] - /// A global counter used in the randomness generation - // TODO: PLMC-155. Remove it after using the Randomness from BABE's VRF: https://github.com/PureStake/moonbeam/issues/1391 - // Or use the randomness from Moonbeam. - pub type Nonce = StorageValue<_, u32, ValueQuery>; - #[pallet::storage] /// A StorageMap containing the primary project information of projects pub type ProjectsMetadata = StorageMap<_, Blake2_128Concat, ProjectId, ProjectMetadataOf>; @@ -623,7 +617,6 @@ pub mod pallet { project_id: ProjectId, account: AccountIdOf, bid_id: u32, - reason: RejectionReason, plmc_amount: BalanceOf, funding_asset: AcceptedFundingAsset, funding_amount: BalanceOf, @@ -888,7 +881,7 @@ pub mod pallet { let (account, _did, investor_type, _cid) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; ensure!(investor_type == InvestorType::Institutional, Error::::WrongInvestorType); - Self::do_start_auction_opening(account, project_id) + Self::do_start_auction(account, project_id) } /// Bond PLMC for a project in the evaluation stage @@ -921,7 +914,7 @@ pub mod pallet { #[pallet::weight(WeightInfoOf::::start_auction_manually(1))] pub fn root_do_auction_opening(origin: OriginFor, project_id: ProjectId) -> DispatchResult { ensure_root(origin)?; - Self::do_start_auction_opening(T::PalletId::get().into_account_truncating(), project_id) + Self::do_start_auction(T::PalletId::get().into_account_truncating(), project_id) } /// Bid for a project in the Auction round @@ -948,18 +941,6 @@ pub mod pallet { Self::do_bid(&account, project_id, ct_amount, multiplier, asset, did, investor_type, whitelisted_policy) } - #[pallet::call_index(9)] - #[pallet::weight(WeightInfoOf::::start_auction_closing_phase( - 1, - ))] - pub fn root_do_start_auction_closing( - origin: OriginFor, - project_id: ProjectId, - ) -> DispatchResult { - ensure_root(origin)?; - Self::do_start_auction_closing(project_id) - } - #[pallet::call_index(10)] #[pallet::weight(WeightInfoOf::::end_auction_closing( 1, @@ -976,30 +957,9 @@ pub mod pallet { 0u32, ::MaxBidsPerProject::get(), )))] - pub fn root_do_end_auction_closing(origin: OriginFor, project_id: ProjectId) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - Self::do_end_auction_closing(project_id) - } - - #[pallet::call_index(11)] - #[pallet::weight(WeightInfoOf::::start_community_funding( - 1, - ::MaxBidsPerProject::get() / 2, - ::MaxBidsPerProject::get() / 2, - ) - .max(WeightInfoOf::::start_community_funding( - 1, - ::MaxBidsPerProject::get(), - 0u32, - )) - .max(WeightInfoOf::::start_community_funding( - 1, - 0u32, - ::MaxBidsPerProject::get(), - )))] - pub fn root_do_community_funding(origin: OriginFor, project_id: ProjectId) -> DispatchResultWithPostInfo { + pub fn root_do_end_auction(origin: OriginFor, project_id: ProjectId) -> DispatchResultWithPostInfo { ensure_root(origin)?; - Self::do_start_community_funding(project_id) + Self::do_end_auction(project_id) } /// Buy tokens in the Community or Remainder round at the price set in the Auction Round diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 8322f349a..20c3b210e 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -228,9 +228,8 @@ mod round_flow { let evaluation_end = inst .get_project_details(project_id) - .phase_transition_points - .evaluation - .end + .round_duration + .end() .expect("Evaluation round end block should be set"); inst.evaluate_for_users(project_id, default_failing_evaluations()).expect("Bonding should work"); diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index e2a3dde2a..a3ad94c8b 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -392,12 +392,12 @@ mod round_flow { let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let details = inst.get_project_details(project_id); - let opening_end = details.phase_transition_points.auction_opening.end().unwrap(); + let opening_end = details.round_duration.end().unwrap(); let now = inst.current_block(); inst.advance_time(opening_end - now + 2).unwrap(); let details = inst.get_project_details(project_id); - let closing_end = details.phase_transition_points.auction_closing.end().unwrap(); + let closing_end = details.round_duration.end().unwrap(); let now = inst.current_block(); inst.advance_time(closing_end - now + 2).unwrap(); @@ -442,15 +442,15 @@ mod round_flow { inst.start_community_funding(project_id).unwrap(); let stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); - let non_rejected_bids = stored_bids - .into_iter() - .filter(|bid| { - (bid.final_ct_amount == 0 && bid.status == BidStatus::Rejected(RejectionReason::AfterClosingEnd)) - .not() - }) - .count(); - assert_eq!(non_rejected_bids, 0); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound); + // let non_rejected_bids = stored_bids + // .into_iter() + // .filter(|bid| { + // (bid.final_ct_amount == 0 && bid.status == BidStatus::Rejected(RejectionReason::RejectionReason)) + // .not() + // }) + // .count(); + // assert_eq!(non_rejected_bids, 0); + // assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound); } #[test] @@ -808,8 +808,8 @@ mod start_auction_extrinsic { inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); inst.advance_time(1).unwrap(); - inst.execute(|| Pallet::::do_start_auction_opening(ISSUER_1, project_id)).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionOpening); + inst.execute(|| Pallet::::do_start_auction(ISSUER_1, project_id)).unwrap(); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); } #[test] @@ -826,7 +826,7 @@ mod start_auction_extrinsic { for account in 6000..6010 { inst.execute(|| { - let response = Pallet::::do_start_auction_opening(account, project_id); + let response = Pallet::::do_start_auction(account, project_id); assert_noop!(response, Error::::NotIssuer); }); } @@ -843,7 +843,7 @@ mod start_auction_extrinsic { let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); inst.execute(|| { assert_noop!( - PolimecFunding::do_start_auction_opening(ISSUER_1, project_id), + PolimecFunding::do_start_auction(ISSUER_1, project_id), Error::::TransitionPointNotSet ); }); @@ -856,7 +856,7 @@ mod start_auction_extrinsic { inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); inst.execute(|| { assert_noop!( - PolimecFunding::do_start_auction_opening(ISSUER_1, project_id), + PolimecFunding::do_start_auction(ISSUER_1, project_id), Error::::TransitionPointNotSet ); }); diff --git a/pallets/funding/src/tests/4_community.rs b/pallets/funding/src/tests/4_community.rs index 18649b4e7..af97d7021 100644 --- a/pallets/funding/src/tests/4_community.rs +++ b/pallets/funding/src/tests/4_community.rs @@ -472,7 +472,7 @@ mod community_contribute_extrinsic { let auction_end = ::AuctionOpeningDuration::get() + ::AuctionClosingDuration::get(); inst.advance_time(auction_end - 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionClosing); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); inst.bid_for_users(project_id, vec![bob_bid]).unwrap(); inst.start_community_funding(project_id).unwrap(); diff --git a/pallets/funding/src/tests/5_remainder.rs b/pallets/funding/src/tests/5_remainder.rs index ed4769f03..0c2e68e08 100644 --- a/pallets/funding/src/tests/5_remainder.rs +++ b/pallets/funding/src/tests/5_remainder.rs @@ -439,7 +439,7 @@ mod remaining_contribute_extrinsic { let auction_end = ::AuctionOpeningDuration::get() + ::AuctionClosingDuration::get(); inst.advance_time(auction_end - 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionClosing); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); inst.bid_for_users(project_id, vec![bob_bid]).unwrap(); inst.start_community_funding(project_id).unwrap(); diff --git a/pallets/funding/src/tests/6_funding_end.rs b/pallets/funding/src/tests/6_funding_end.rs index 11df4b587..f8d9a8f6f 100644 --- a/pallets/funding/src/tests/6_funding_end.rs +++ b/pallets/funding/src/tests/6_funding_end.rs @@ -337,9 +337,8 @@ mod decide_project_outcome { call_fails(project_id, ISSUER_3, &mut inst); // Auction - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_4, None, default_evaluations()); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionOpening); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_4, default_evaluations()); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); call_fails(project_id, ISSUER_4, &mut inst); // Community diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs index a313f112d..2d558c2e4 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -221,6 +221,26 @@ mod helper_functions { } } + #[test] + fn bucket_wap_calculation() { + + let initial_price = FixedU128::from_float(10.0); + let mut bucket = Bucket::new(100u32, initial_price, FixedU128::from_float(1.0), 10u32); + let wap = bucket.calculate_wap(100u32); + assert!(wap == initial_price); + + // Initial token amount: 100 + // Simulate total bidding amount of 128 + bucket.update(100u32); + bucket.update(10u32); + bucket.update(10u32); + bucket.update(8u32); + let wap = bucket.calculate_wap(100u32); + let expected = FixedU128::from_float(10.628); + let diff = if wap > expected { wap - expected } else { expected - wap }; + assert!(diff <= FixedU128::from_float(0.001)); + } + #[test] fn calculate_contributed_plmc_spent() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 2fa50d379..0fb050050 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -457,7 +457,7 @@ pub mod storage_types { pub delta_amount: Balance, } - impl Bucket { + impl Bucket { /// Creates a new bucket with the given parameters. pub const fn new( amount_left: Balance, @@ -483,6 +483,34 @@ pub mod storage_types { self.amount_left = self.delta_amount; self.current_price = self.current_price.saturating_add(self.delta_price); } + + pub fn calculate_wap(self, mut total_amount: Balance) -> Price { + // First bucket is not empty so wap is the same as the initial price + if self.current_price == self.initial_price { + return self.current_price; + } + let mut amount: Balance = self.delta_amount.saturating_sub(self.amount_left); + let mut price: Price = self.current_price; + let mut bucket_sizes: Vec<(Balance, Price)> = Vec::new(); + while price > self.initial_price && total_amount > Balance::zero() { + total_amount.saturating_reduce(amount); + bucket_sizes.push((price.saturating_mul_int(amount), price)); + price = price.saturating_sub(self.delta_price); + amount = self.delta_amount; + } + + if total_amount > Balance::zero() { + bucket_sizes.push((self.initial_price.saturating_mul_int(total_amount), self.initial_price)); + } + + let sum = bucket_sizes.iter().map(|x| x.0).fold(Balance::zero(), |acc, x| acc.saturating_add(x)); + + let wap = bucket_sizes.into_iter() + .map(|x| ::saturating_from_rational(x.0, sum).saturating_mul(x.1) ) + .fold(Price::zero(), |acc: Price, p: Price| acc.saturating_add(p)); + + return wap + } } } @@ -670,9 +698,7 @@ pub mod inner_types { Application, EvaluationRound, AuctionInitializePeriod, - AuctionOpening, - AuctionClosing, - CalculatingWAP, + Auction, CommunityRound, RemainderRound, FundingFailed, @@ -741,21 +767,12 @@ pub mod inner_types { YetUnknown, /// The bid is accepted Accepted, - /// The bid is rejected, and the reason is provided - Rejected(RejectionReason), - /// The bid is partially accepted. The amount accepted and reason for rejection are provided - PartiallyAccepted(Balance, RejectionReason), + /// The bid is rejected because the ct tokens ran out + Rejected, + /// The bid is partially accepted as there were not enough tokens to fill the full bid + PartiallyAccepted(Balance), } - #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub enum RejectionReason { - /// The bid was submitted after the closing period ended - AfterClosingEnd, - /// The bid was accepted but too many tokens were requested. A partial amount was accepted - NoTokensLeft, - /// Error in calculating ticket_size for partially funded request - BadMath, - } #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct VestingInfo { @@ -768,9 +785,7 @@ pub mod inner_types { pub enum ProjectPhases { Evaluation, AuctionInitializePeriod, - AuctionOpening, - AuctionClosing, - CalculatingWAP, + Auction, CommunityFunding, RemainderFunding, DecisionPeriod, diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 8e6ca7358..549ad3696 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -247,7 +247,7 @@ impl Contains for BaseCallFilter { pallet_funding::Call::root_do_auction_opening { .. } | pallet_funding::Call::root_do_start_auction_closing { .. } | pallet_funding::Call::bid { .. } | - pallet_funding::Call::root_do_end_auction_closing { .. } | + pallet_funding::Call::root_do_end_auction { .. } | pallet_funding::Call::community_contribute { .. } | pallet_funding::Call::remaining_contribute { .. } | pallet_funding::Call::decide_project_outcome { .. } | From bb925d7387b0782b159957bebb13ca54f705c08f Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 15 Jul 2024 11:53:52 +0200 Subject: [PATCH 3/7] :recycle: merge community/remainder round into single round with transition point stored in CommunityRoundStatus --- pallets/funding/README.md | 3 +- pallets/funding/src/benchmarking.rs | 9 +- pallets/funding/src/functions/3_auction.rs | 5 +- .../funding/src/functions/4_contribution.rs | 130 +++--------------- .../funding/src/functions/5_funding_end.rs | 3 +- pallets/funding/src/functions/6_settlement.rs | 2 +- pallets/funding/src/functions/misc.rs | 6 +- .../src/instantiator/chain_interactions.rs | 43 +----- pallets/funding/src/instantiator/types.rs | 2 +- pallets/funding/src/lib.rs | 58 +------- pallets/funding/src/mock.rs | 4 +- pallets/funding/src/tests/3_auction.rs | 4 +- pallets/funding/src/tests/4_community.rs | 60 ++++---- pallets/funding/src/tests/5_remainder.rs | 62 ++++----- pallets/funding/src/tests/6_funding_end.rs | 3 +- pallets/funding/src/tests/7_settlement.rs | 4 +- pallets/funding/src/types.rs | 7 +- pallets/sandbox/src/lib.rs | 2 +- runtimes/polimec/src/lib.rs | 4 +- 19 files changed, 114 insertions(+), 297 deletions(-) diff --git a/pallets/funding/README.md b/pallets/funding/README.md index 69ffc899a..08584dacd 100644 --- a/pallets/funding/README.md +++ b/pallets/funding/README.md @@ -45,8 +45,7 @@ Basic flow of a project's lifecycle: | Closing Auction Transition | After the [`Config::AuctionOpeningDuration`] has passed, the auction goes into closing mode thanks to [`on_initialize()`](Pallet::on_initialize) | [`AuctionClosing`](ProjectStatus::AuctionClosing) | | Bid Submissions | Institutional and Professional users can continue bidding, but this time their bids will only be considered, if they managed to fall before the random ending block calculated at the end of the auction. | [`AuctionClosing`](ProjectStatus::AuctionClosing) | | Community Funding Start | After the [`Config::AuctionClosingDuration`] has passed, the auction automatically. A final token price for the next rounds is calculated based on the accepted bids. | [`CommunityRound`](ProjectStatus::CommunityRound) | -| Funding Submissions | Retail investors can call the [`contribute()`](Pallet::contribute) extrinsic to buy tokens at the set price. | [`CommunityRound`](ProjectStatus::CommunityRound) | -| Remainder Funding Start | After the [`Config::CommunityFundingDuration`] has passed, the project is now open to token purchases from any user type | [`RemainderRound`](ProjectStatus::RemainderRound) | +| Funding Submissions | Retail investors can call the [`contribute()`](Pallet::contribute) extrinsic to buy tokens at the set price. | [`CommunityRound`](ProjectStatus::CommunityRound) | | | Funding End | If all tokens were sold, or after the [`Config::RemainderFundingDuration`] has passed, the project automatically ends, and it is calculated if it reached its desired funding or not. | [`FundingEnded`](ProjectStatus::FundingEnded) | | Evaluator Rewards | If the funding was successful, evaluators can claim their contribution token rewards with the [`TBD`]() extrinsic. If it failed, evaluators can either call the [`failed_evaluation_unbond_for()`](Pallet::failed_evaluation_unbond_for) extrinsic, or wait for the [`on_idle()`](Pallet::on_initialize) function, to return their funds | [`FundingEnded`](ProjectStatus::FundingEnded) | | Bidder Rewards | If the funding was successful, bidders will call [`vested_contribution_token_bid_mint_for()`](Pallet::vested_contribution_token_bid_mint_for) to mint the contribution tokens they are owed, and [`vested_plmc_bid_unbond_for()`](Pallet::vested_plmc_bid_unbond_for) to unbond their PLMC, based on their current vesting schedule. | [`FundingEnded`](ProjectStatus::FundingEnded) | diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index cd51f1f82..cd88c07cf 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -1293,7 +1293,7 @@ mod benchmarks { ); #[extrinsic_call] - community_contribute( + contribute( RawOrigin::Signed(extrinsic_contribution.contributor.clone()), jwt, project_id, @@ -1344,7 +1344,7 @@ mod benchmarks { ); #[extrinsic_call] - community_contribute( + contribute( RawOrigin::Signed(extrinsic_contribution.contributor.clone()), jwt, project_id, @@ -2311,7 +2311,6 @@ mod benchmarks { // * validity checks * // Storage let stored_details = ProjectsDetails::::get(project_id).unwrap(); - assert_eq!(stored_details.status, ProjectStatus::RemainderRound); // Events frame_system::Pallet::::assert_last_event( @@ -2362,7 +2361,6 @@ mod benchmarks { ); let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.status, ProjectStatus::RemainderRound); let last_funding_block = project_details.phase_transition_points.remainder.end().unwrap(); frame_system::Pallet::::set_block_number(last_funding_block + 1u32.into()); @@ -2421,7 +2419,6 @@ mod benchmarks { ); let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.status, ProjectStatus::RemainderRound); let last_funding_block = project_details.phase_transition_points.remainder.end().unwrap(); frame_system::Pallet::::set_block_number(last_funding_block + 1u32.into()); @@ -2481,7 +2478,6 @@ mod benchmarks { ); let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.status, ProjectStatus::RemainderRound); let last_funding_block = project_details.phase_transition_points.remainder.end().unwrap(); frame_system::Pallet::::set_block_number(last_funding_block + 1u32.into()); @@ -2559,7 +2555,6 @@ mod benchmarks { ); let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.status, ProjectStatus::RemainderRound); let last_funding_block = project_details.phase_transition_points.remainder.end().unwrap(); frame_system::Pallet::::set_block_number(last_funding_block + 1u32.into()); diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index 8eefc3489..398a8aa3c 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -63,13 +63,14 @@ impl Pallet { match calculation_result { Err(e) => return Err(DispatchErrorWithPostInfo { post_info: ().into(), error: e }), Ok((accepted_bids_count, rejected_bids_count)) => { + let now = >::block_number(); // * Transition Round * Self::transition_project( project_id, project_details, ProjectStatus::Auction, - ProjectStatus::CommunityRound, - T::CommunityFundingDuration::get(), + ProjectStatus::CommunityRound(now.saturating_add(T::CommunityFundingDuration::get())), + T::CommunityFundingDuration::get() + T::RemainderFundingDuration::get(), false, )?; Ok(PostDispatchInfo { diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs index 4b03d2699..6dd1c7b70 100644 --- a/pallets/funding/src/functions/4_contribution.rs +++ b/pallets/funding/src/functions/4_contribution.rs @@ -1,47 +1,6 @@ use super::*; impl Pallet { - /// Called automatically by on_initialize - /// Starts the remainder round for a project. - /// Anyone can now buy tokens, until they are all sold out, or the time is reached. - /// - /// # Arguments - /// * `project_id` - The project identifier - /// - /// # Storage access - /// * [`ProjectsDetails`] - Get the project information, and check if the project is in the correct - /// round, the current block is after the community funding end period, and there are still tokens left to sell. - /// Update the project information with the new round status and transition points in case of success. - /// - /// # Success Path - /// The validity checks pass, and the project is transitioned to the Remainder Funding round. - /// The project is scheduled to be transitioned automatically by `on_initialize` at the end of the - /// round. - /// - /// # Next step - /// Any users can now buy tokens at the price set on the auction round. - /// Later on, `on_initialize` ends the remainder round, and finalizes the project funding, by calling - /// [`do_end_funding`](Self::do_end_funding). - #[transactional] - pub fn do_start_remainder_funding(project_id: ProjectId) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - // Transition to remainder round was initiated by `do_community_funding`, but the ct - // tokens where already sold in the community round. This transition is obsolete. - ensure!( - project_details.remaining_contribution_tokens > 0u32.into(), - Error::::RoundTransitionAlreadyHappened - ); - - Self::transition_project( - project_id, - project_details, - ProjectStatus::CommunityRound, - ProjectStatus::RemainderRound, - T::RemainderFundingDuration::get(), - false, - ) - } /// Buy tokens in the Community Round at the price set in the Bidding Round /// @@ -53,7 +12,7 @@ impl Pallet { /// * multiplier: Decides how much PLMC bonding is required for buying that amount of tokens /// * asset: The asset used for the contribution #[transactional] - pub fn do_community_contribute( + pub fn do_contribute( contributor: &AccountIdOf, project_id: ProjectId, token_amount: BalanceOf, @@ -65,14 +24,21 @@ impl Pallet { ) -> DispatchResultWithPostInfo { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let did_has_winning_bid = DidWithWinningBids::::get(project_id, did.clone()); - - ensure!(project_details.status == ProjectStatus::CommunityRound, Error::::IncorrectRound); - ensure!(!did_has_winning_bid, Error::::UserHasWinningBid); + let round_start_block = project_details.round_duration.start().ok_or(Error::::ImpossibleState)?; + + let remainder_start = match project_details.status { + ProjectStatus::CommunityRound(remainder_start) => remainder_start, + _ => return Err(Error::::IncorrectRound.into()), + }; + + let now = >::block_number(); + let remainder_started = now > remainder_start; + ensure!(!did_has_winning_bid || remainder_started, Error::::UserHasWinningBid); let buyable_tokens = token_amount.min(project_details.remaining_contribution_tokens); project_details.remaining_contribution_tokens.saturating_reduce(buyable_tokens); - Self::do_contribute( + Self::do_perform_contribution( contributor, project_id, &mut project_details, @@ -85,50 +51,8 @@ impl Pallet { ) } - /// Buy tokens in the Community Round at the price set in the Bidding Round - /// - /// # Arguments - /// * contributor: The account that is buying the tokens - /// * project_id: The identifier of the project - /// * token_amount: The amount of contribution tokens the contributor tries to buy. Tokens - /// are limited by the total amount of tokens available after the Auction and Community rounds. - /// * multiplier: Decides how much PLMC bonding is required for buying that amount of tokens - /// * asset: The asset used for the contribution - #[transactional] - pub fn do_remaining_contribute( - contributor: &AccountIdOf, - project_id: ProjectId, - token_amount: BalanceOf, - multiplier: MultiplierOf, - asset: AcceptedFundingAsset, - did: Did, - investor_type: InvestorType, - whitelisted_policy: Cid, - ) -> DispatchResultWithPostInfo { - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - - ensure!(project_details.status == ProjectStatus::RemainderRound, Error::::IncorrectRound); - let buyable_tokens = token_amount.min(project_details.remaining_contribution_tokens); - - let before = project_details.remaining_contribution_tokens; - let remaining_cts_in_round = before.saturating_sub(buyable_tokens); - project_details.remaining_contribution_tokens = remaining_cts_in_round; - - Self::do_contribute( - contributor, - project_id, - &mut project_details, - token_amount, - multiplier, - asset, - investor_type, - did, - whitelisted_policy, - ) - } - #[transactional] - fn do_contribute( + fn do_perform_contribution( contributor: &AccountIdOf, project_id: ProjectId, project_details: &mut ProjectDetailsOf, @@ -145,13 +69,6 @@ impl Pallet { let total_usd_bought_by_did = ContributionBoughtUSD::::get((project_id, did.clone())); let now = >::block_number(); let ct_usd_price = project_details.weighted_average_price.ok_or(Error::::WapNotSet)?; - - let funding_asset_id = funding_asset.to_assethub_id(); - let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id); - let funding_asset_usd_price = - T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) - .ok_or(Error::::PriceNotFound)?; - let project_policy = project_metadata.policy_ipfs_cid.ok_or(Error::::ImpossibleState)?; let ticket_size = ct_usd_price.checked_mul_int(buyable_tokens).ok_or(Error::::BadMath)?; @@ -186,16 +103,14 @@ impl Pallet { caller_existing_contributions.len() < T::MaxContributionsPerUser::get() as usize, Error::::TooManyUserParticipations ); - ensure!(contributor_ticket_size.usd_ticket_above_minimum_per_participation(ticket_size), Error::::TooLow); + ensure!(contributor_ticket_size.usd_ticket_above_minimum_per_participation(ticket_size) || project_details.remaining_contribution_tokens.is_zero(), Error::::TooLow); ensure!( contributor_ticket_size.usd_ticket_below_maximum_per_did(total_usd_bought_by_did + ticket_size), Error::::TooHigh ); let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier)?; - let funding_asset_amount = - funding_asset_usd_price.reciprocal().ok_or(Error::::BadMath)?.saturating_mul_int(ticket_size); - let asset_id = funding_asset.to_assethub_id(); + let funding_asset_amount = Self::calculate_funding_asset_amount(ticket_size, funding_asset)?; let contribution_id = NextContributionId::::get(); let new_contribution = ContributionInfoOf:: { @@ -214,18 +129,14 @@ impl Pallet { // Try adding the new contribution to the system Self::try_plmc_participation_lock(contributor, project_id, plmc_bond)?; - Self::try_funding_asset_hold(contributor, project_id, funding_asset_amount, asset_id)?; + Self::try_funding_asset_hold(contributor, project_id, funding_asset_amount, funding_asset.to_assethub_id())?; Contributions::::insert((project_id, contributor, contribution_id), &new_contribution); NextContributionId::::set(contribution_id.saturating_add(One::one())); ContributionBoughtUSD::::mutate((project_id, did), |amount| *amount += ticket_size); - let remaining_cts_after_purchase = project_details.remaining_contribution_tokens; project_details.funding_amount_reached_usd.saturating_accrue(new_contribution.usd_contribution_amount); ProjectsDetails::::insert(project_id, project_details); - // If no CTs remain, end the funding phase - - let mut weight_round_end_flag: Option = None; // * Emit events * Self::deposit_event(Event::Contribution { @@ -240,14 +151,7 @@ impl Pallet { }); // return correct weight function - let actual_weight = match weight_round_end_flag { - None => Some(WeightInfoOf::::contribution(caller_existing_contributions.len() as u32)), - Some(fully_filled_vecs_from_insertion) => Some(WeightInfoOf::::contribution_ends_round( - caller_existing_contributions.len() as u32, - fully_filled_vecs_from_insertion, - )), - }; - + let actual_weight = Some(WeightInfoOf::::contribution(caller_existing_contributions.len() as u32)); Ok(PostDispatchInfo { actual_weight, pays_fee: Pays::Yes }) } } diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/5_funding_end.rs index 2bdd76036..7f23fa41f 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -54,8 +54,7 @@ impl Pallet { !matches!( project_details.status, ProjectStatus::FundingSuccessful | - ProjectStatus::FundingFailed | - ProjectStatus::AwaitingProjectDecision + ProjectStatus::FundingFailed ), Error::::RoundTransitionAlreadyHappened ); diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index 8606b34db..0d1ddea33 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -259,7 +259,7 @@ impl Pallet { bid.final_ct_usd_price.checked_mul_int(bid.final_ct_amount).ok_or(Error::::BadMath)?; let new_plmc_bond = Self::calculate_plmc_bond(new_ticket_size, bid.multiplier)?; - let new_funding_asset_amount = Self::calculate_funding_asset_lock(new_ticket_size, bid.funding_asset)?; + let new_funding_asset_amount = Self::calculate_funding_asset_amount(new_ticket_size, bid.funding_asset)?; let refund_plmc = bid.plmc_bond.saturating_sub(new_plmc_bond); let refund_funding_asset = bid.funding_asset_amount_locked.saturating_sub(new_funding_asset_amount); if T::FundingCurrency::can_deposit(bid.funding_asset.to_assethub_id(), &bid.bidder, refund_funding_asset, Provenance::Extant) != DepositConsequence::Success { diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 80e811ce6..b988ff573 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -39,7 +39,7 @@ impl Pallet { plmc_usd_price.reciprocal().ok_or(Error::::BadMath)?.checked_mul_int(usd_bond).ok_or(Error::::BadMath.into()) } - pub fn calculate_funding_asset_lock( + pub fn calculate_funding_asset_amount( ticket_size: BalanceOf, asset_id: AcceptedFundingAsset, ) -> Result, DispatchError> { @@ -423,8 +423,8 @@ impl Pallet { pub(crate) fn transition_project( project_id: ProjectId, mut project_details: ProjectDetailsOf, - current_round: ProjectStatus, - next_round: ProjectStatus, + current_round: ProjectStatus>, + next_round: ProjectStatus>, round_duration: BlockNumberFor, skip_end_check: bool, ) -> DispatchResult { diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index db4e061bd..4d38b9d3c 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -540,7 +540,7 @@ impl< } ensure!( - self.get_project_details(project_id).status == ProjectStatus::CommunityRound, + matches!(self.get_project_details(project_id).status, ProjectStatus::CommunityRound(..)), DispatchError::from("Auction failed") ); @@ -634,29 +634,12 @@ impl< let project_policy = self.get_project_metadata(project_id).policy_ipfs_cid.unwrap(); match self.get_project_details(project_id).status { - ProjectStatus::CommunityRound => + ProjectStatus::CommunityRound(..) => for cont in contributions { let did = generate_did_from_account(cont.contributor.clone()); let investor_type = InvestorType::Retail; self.execute(|| { - crate::Pallet::::do_community_contribute( - &cont.contributor, - project_id, - cont.amount, - cont.multiplier, - cont.asset, - did, - investor_type, - project_policy.clone(), - ) - })?; - }, - ProjectStatus::RemainderRound => - for cont in contributions { - let did = generate_did_from_account(cont.contributor.clone()); - let investor_type = InvestorType::Professional; - self.execute(|| { - crate::Pallet::::do_remaining_contribute( + crate::Pallet::::do_contribute( &cont.contributor, project_id, cont.amount, @@ -676,7 +659,7 @@ impl< pub fn start_remainder_or_end_funding(&mut self, project_id: ProjectId) -> Result<(), DispatchError> { let details = self.get_project_details(project_id); - assert_eq!(details.status, ProjectStatus::CommunityRound); + assert!(matches!(details.status, ProjectStatus::CommunityRound(..))); let remaining_tokens = details.remaining_contribution_tokens; let update_type = if remaining_tokens > Zero::zero() { UpdateType::RemainderFundingStart } else { UpdateType::FundingEnd }; @@ -684,7 +667,7 @@ impl< self.execute(|| frame_system::Pallet::::set_block_number(transition_block - One::one())); self.advance_time(1u32.into()).unwrap(); match self.get_project_details(project_id).status { - ProjectStatus::RemainderRound | ProjectStatus::FundingSuccessful => Ok(()), + ProjectStatus::FundingSuccessful => Ok(()), _ => panic!("Bad state"), } } else { @@ -1050,10 +1033,6 @@ impl< match self.get_project_details(project_id).status { ProjectStatus::FundingSuccessful => return project_id, - ProjectStatus::RemainderRound if remainder_contributions.is_empty() => { - self.finish_funding(project_id, None).unwrap(); - return project_id; - }, _ => {}, }; let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); @@ -1170,7 +1149,7 @@ impl< pub fn create_project_at( &mut self, - status: ProjectStatus, + status: ProjectStatus>, project_metadata: ProjectMetadataOf, issuer: AccountIdOf, evaluations: Vec>, @@ -1188,15 +1167,7 @@ impl< community_contributions, remainder_contributions, ), - ProjectStatus::RemainderRound => self.create_remainder_contributing_project( - project_metadata, - issuer, - None, - evaluations, - bids, - community_contributions, - ), - ProjectStatus::CommunityRound => + ProjectStatus::CommunityRound(..) => self.create_community_contributing_project(project_metadata, issuer, evaluations, bids), ProjectStatus::Auction => self.create_auctioning_project(project_metadata, issuer, evaluations), ProjectStatus::EvaluationRound => self.create_evaluating_project(project_metadata, issuer), diff --git a/pallets/funding/src/instantiator/types.rs b/pallets/funding/src/instantiator/types.rs index 866513dfa..738c8a720 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -12,7 +12,7 @@ impl Default for BoxToFunction { #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))] pub struct TestProjectParams { - pub expected_state: ProjectStatus, + pub expected_state: ProjectStatus>, pub metadata: ProjectMetadataOf, pub issuer: AccountIdOf, pub evaluations: Vec>, diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 47ee90763..e9aaa6b2f 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -53,7 +53,6 @@ //! | Bid Submissions | Institutional and Professional users can continue bidding, but this time their bids will only be considered, if they managed to fall before the random ending block calculated at the end of the auction. | [`AuctionClosing`](ProjectStatus::AuctionClosing) | //! | Community Funding Start | After the [`Config::AuctionClosingDuration`] has passed, the auction automatically. A final token price for the next rounds is calculated based on the accepted bids. | [`CommunityRound`](ProjectStatus::CommunityRound) | //! | Funding Submissions | Retail investors can call the [`contribute()`](Pallet::contribute) extrinsic to buy tokens at the set price. | [`CommunityRound`](ProjectStatus::CommunityRound) | -//! | Remainder Funding Start | After the [`Config::CommunityFundingDuration`] has passed, the project is now open to token purchases from any user type | [`RemainderRound`](ProjectStatus::RemainderRound) | //! | Funding End | If all tokens were sold, or after the [`Config::RemainderFundingDuration`] has passed, the project automatically ends, and it is calculated if it reached its desired funding or not. | [`FundingEnded`](ProjectStatus::FundingSuccessful) | //! | Evaluator Rewards | If the funding was successful, evaluators can claim their contribution token rewards with the [`TBD`]() extrinsic. If it failed, evaluators can either call the [`failed_evaluation_unbond_for()`](Pallet::failed_evaluation_unbond_for) extrinsic, or wait for the [`on_idle()`](Pallet::on_initialize) function, to return their funds | [`FundingEnded`](ProjectStatus::FundingSuccessful) | //! | Bidder Rewards | If the funding was successful, bidders will call [`vested_contribution_token_bid_mint_for()`](Pallet::vested_contribution_token_bid_mint_for) to mint the contribution tokens they are owed, and [`vested_plmc_bid_unbond_for()`](Pallet::vested_plmc_bid_unbond_for) to unbond their PLMC, based on their current vesting schedule. | [`FundingEnded`](ProjectStatus::FundingSuccessful) | @@ -204,7 +203,7 @@ pub mod pallet { use sp_arithmetic::Percent; use sp_runtime::{ traits::{Convert, ConvertBack, Get}, - DispatchErrorWithPostInfo, + Perquintill, }; #[pallet::composite_enum] @@ -296,6 +295,8 @@ pub mod pallet { + fungibles::metadata::Mutate, AssetId = u32> + fungibles::Mutate, Balance = BalanceOf>; + type FundingSuccessThreshold: Get; + /// Credentialized investor Origin, ensures users are of investing type Retail, or Professional, or Institutional. type InvestorOrigin: EnsureOriginWithCredentials< ::RuntimeOrigin, @@ -966,57 +967,8 @@ pub mod pallet { #[pallet::call_index(12)] #[pallet::weight( WeightInfoOf::::contribution(T::MaxContributionsPerUser::get() - 1) - .max(WeightInfoOf::::contribution_ends_round( - // Last contribution possible before having to remove an old lower one - ::MaxContributionsPerUser::get() -1, - // Since we didn't remove any previous lower contribution, we can buy all remaining CTs and try to move to the next phase - 0, - )) - )] - pub fn community_contribute( - origin: OriginFor, - jwt: UntrustedToken, - project_id: ProjectId, - #[pallet::compact] amount: BalanceOf, - multiplier: MultiplierOf, - asset: AcceptedFundingAsset, - ) -> DispatchResultWithPostInfo { - let (account, did, investor_type, whitelisted_policy) = - T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; - - Self::do_community_contribute( - &account, - project_id, - amount, - multiplier, - asset, - did, - investor_type, - whitelisted_policy, - ) - } - - #[pallet::call_index(13)] - #[pallet::weight(WeightInfoOf::::start_remainder_funding( - 1, - ))] - pub fn root_do_remainder_funding(origin: OriginFor, project_id: ProjectId) -> DispatchResult { - ensure_root(origin)?; - Self::do_start_remainder_funding(project_id) - } - - /// Buy tokens in the Community or Remainder round at the price set in the Auction Round - #[pallet::call_index(14)] - #[pallet::weight( - WeightInfoOf::::contribution(T::MaxContributionsPerUser::get() - 1) - .max(WeightInfoOf::::contribution_ends_round( - // Last contribution possible before having to remove an old lower one - ::MaxContributionsPerUser::get() -1, - // Since we didn't remove any previous lower contribution, we can buy all remaining CTs and try to move to the next phase - 1 - )) )] - pub fn remaining_contribute( + pub fn contribute( origin: OriginFor, jwt: UntrustedToken, project_id: ProjectId, @@ -1027,7 +979,7 @@ pub mod pallet { let (account, did, investor_type, whitelisted_policy) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; - Self::do_remaining_contribute( + Self::do_contribute( &account, project_id, amount, diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index c7afb17ad..9991752e4 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -39,7 +39,7 @@ use sp_arithmetic::Percent; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, ConvertBack, ConvertInto, Get, IdentityLookup, TryConvert}, - BuildStorage, + BuildStorage, Perquintill, }; use sp_std::collections::btree_map::BTreeMap; use std::cell::RefCell; @@ -312,6 +312,7 @@ parameter_types! { pub EvaluatorSlash: Percent = Percent::from_percent(20); pub BlockchainOperationTreasuryAccount: AccountId = AccountId::from(696969u32); pub ContributionTreasury: AccountId = AccountId::from(4204204206u32); + pub FundingSuccessThreshold: Perquintill = Perquintill::from_percent(33); } parameter_types! { @@ -413,6 +414,7 @@ impl Config for TestRuntime { type EvaluatorSlash = EvaluatorSlash; type FeeBrackets = FeeBrackets; type FundingCurrency = ForeignAssets; + type FundingSuccessThreshold = FundingSuccessThreshold; type InvestorOrigin = EnsureInvestor; type ManualAcceptanceDuration = ManualAcceptanceDuration; type MaxBidsPerProject = ConstU32<512>; diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index a3ad94c8b..a524a6ca1 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -402,7 +402,7 @@ mod round_flow { inst.advance_time(closing_end - now + 2).unwrap(); let details = inst.get_project_details(project_id); - assert_eq!(details.status, ProjectStatus::CommunityRound); + assert!(matches!(details.status, ProjectStatus::CommunityRound(..))); assert_eq!(details.weighted_average_price, Some(project_metadata.minimum_price)); } @@ -753,7 +753,7 @@ mod round_flow { let investor_type = InvestorType::Retail; inst.execute(|| { assert_noop!( - PolimecFunding::do_community_contribute( + PolimecFunding::do_contribute( &BIDDER_1, project_id, 100, diff --git a/pallets/funding/src/tests/4_community.rs b/pallets/funding/src/tests/4_community.rs index af97d7021..49026404e 100644 --- a/pallets/funding/src/tests/4_community.rs +++ b/pallets/funding/src/tests/4_community.rs @@ -280,7 +280,7 @@ mod round_flow { funding_asset.to_assethub_id(), )]); - assert_ok!(inst.execute(|| PolimecFunding::community_contribute( + assert_ok!(inst.execute(|| PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_1), get_mock_jwt_with_cid( BUYER_1, @@ -321,7 +321,7 @@ mod round_flow { } #[cfg(test)] -mod community_contribute_extrinsic { +mod contribute_extrinsic { use super::*; #[cfg(test)] @@ -372,7 +372,7 @@ mod community_contribute_extrinsic { // Can't contribute with only the evaluation bond inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BOB), get_mock_jwt_with_cid( BOB, @@ -394,7 +394,7 @@ mod community_contribute_extrinsic { inst.calculate_contributed_funding_asset_spent(vec![(BOB, usable_ct / 2).into()], ct_price); inst.mint_foreign_asset_to(contribution_usdt.clone()); inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BOB), get_mock_jwt_with_cid( BOB, @@ -414,7 +414,7 @@ mod community_contribute_extrinsic { inst.calculate_contributed_funding_asset_spent(vec![(CARL, usable_ct).into()], ct_price); inst.mint_foreign_asset_to(contribution_usdt.clone()); inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(CARL), get_mock_jwt_with_cid( CARL, @@ -476,7 +476,7 @@ mod community_contribute_extrinsic { inst.bid_for_users(project_id, vec![bob_bid]).unwrap(); inst.start_community_funding(project_id).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound); + assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); let plmc_price = ::PriceProvider::get_decimals_aware_price( PLMC_FOREIGN_ID, @@ -493,7 +493,7 @@ mod community_contribute_extrinsic { let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![bob_contribution], wap); inst.mint_foreign_asset_to(contribution_usdt.clone()); inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(bob), get_mock_jwt_with_cid( bob, @@ -589,7 +589,7 @@ mod community_contribute_extrinsic { inst.mint_foreign_asset_to(necessary_usdt.clone()); } inst.execute(|| { - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(contributor), jwt, project_id, @@ -810,7 +810,7 @@ mod community_contribute_extrinsic { let mut bid_should_succeed = |account, investor_type, did_acc| { inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(account), get_mock_jwt_with_cid( account, @@ -897,7 +897,7 @@ mod community_contribute_extrinsic { }); inst.execute(|| { - assert_ok!(PolimecFunding::community_contribute( + assert_ok!(PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_4), get_mock_jwt_with_cid( BUYER_4, @@ -981,7 +981,7 @@ mod community_contribute_extrinsic { }); inst.execute(|| { - assert_ok!(PolimecFunding::community_contribute( + assert_ok!(PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_4), get_mock_jwt_with_cid( BUYER_4, @@ -1152,7 +1152,7 @@ mod community_contribute_extrinsic { default_bids(), ); assert_err!( - inst.execute(|| crate::Pallet::::do_community_contribute( + inst.execute(|| crate::Pallet::::do_contribute( &(&ISSUER_1 + 1), project_id, 500 * CT_UNIT, @@ -1190,7 +1190,7 @@ mod community_contribute_extrinsic { let mut bid_should_fail = |account, investor_type, did_acc| { inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(account), get_mock_jwt_with_cid( account, @@ -1274,7 +1274,7 @@ mod community_contribute_extrinsic { ); inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_1), jwt, project_id, @@ -1294,7 +1294,7 @@ mod community_contribute_extrinsic { ); inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_2), jwt, project_id, @@ -1315,7 +1315,7 @@ mod community_contribute_extrinsic { ); inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_3), jwt, project_id, @@ -1379,7 +1379,7 @@ mod community_contribute_extrinsic { ); // total contributions with same DID above 10k CT (100k USD) should fail for retail inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BUYER_1), buyer_1_jwt, project_id, @@ -1390,7 +1390,7 @@ mod community_contribute_extrinsic { }); inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_2), buyer_2_jwt_same_did.clone(), project_id, @@ -1403,7 +1403,7 @@ mod community_contribute_extrinsic { }); // bidding 2k total works inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BUYER_2), buyer_2_jwt_same_did, project_id, @@ -1427,7 +1427,7 @@ mod community_contribute_extrinsic { ); // total contributions with same DID above 2k CT (20k USD) should fail for professionals inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BUYER_3), buyer_3_jwt, project_id, @@ -1438,7 +1438,7 @@ mod community_contribute_extrinsic { }); inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_4), buyer_4_jwt_same_did.clone(), project_id, @@ -1451,7 +1451,7 @@ mod community_contribute_extrinsic { }); // bidding 2k total works inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BUYER_4), buyer_4_jwt_same_did, project_id, @@ -1475,7 +1475,7 @@ mod community_contribute_extrinsic { ); // total contributions with same DID above 5k CT (50 USD) should fail for institutionals inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BUYER_5), buyer_5_jwt, project_id, @@ -1486,7 +1486,7 @@ mod community_contribute_extrinsic { }); inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_6), buyer_6_jwt_same_did.clone(), project_id, @@ -1499,7 +1499,7 @@ mod community_contribute_extrinsic { }); // bidding 5k total works inst.execute(|| { - assert_ok!(Pallet::::community_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BUYER_6), buyer_6_jwt_same_did, project_id, @@ -1542,7 +1542,7 @@ mod community_contribute_extrinsic { inst.mint_foreign_asset_to(foreign_funding.clone()); inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_1), jwt.clone(), project_id, @@ -1577,7 +1577,7 @@ mod community_contribute_extrinsic { inst.execute(|| { assert_noop!( - Pallet::::community_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_1), jwt, project_id, @@ -1625,7 +1625,7 @@ mod community_contribute_extrinsic { let project_policy = inst.get_project_metadata(project).policy_ipfs_cid.unwrap(); inst.execute(|| { assert_noop!( - PolimecFunding::community_contribute( + PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_1), get_mock_jwt_with_cid( BUYER_1, @@ -1755,7 +1755,7 @@ mod community_contribute_extrinsic { inst.execute(|| { assert_noop!( - PolimecFunding::community_contribute( + PolimecFunding::contribute( RuntimeOrigin::signed(evaluator_contributor), get_mock_jwt_with_cid( evaluator_contributor, @@ -1787,7 +1787,7 @@ mod community_contribute_extrinsic { inst.execute(|| { assert_noop!( - PolimecFunding::community_contribute( + PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_1), get_mock_jwt_with_cid( BUYER_1, diff --git a/pallets/funding/src/tests/5_remainder.rs b/pallets/funding/src/tests/5_remainder.rs index 0c2e68e08..c44f5e324 100644 --- a/pallets/funding/src/tests/5_remainder.rs +++ b/pallets/funding/src/tests/5_remainder.rs @@ -246,7 +246,7 @@ mod round_flow { funding_asset.to_assethub_id(), )]); - assert_ok!(inst.execute(|| PolimecFunding::remaining_contribute( + assert_ok!(inst.execute(|| PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_1), get_mock_jwt_with_cid( BUYER_1, @@ -287,7 +287,7 @@ mod round_flow { } #[cfg(test)] -mod remaining_contribute_extrinsic { +mod contribute_extrinsic { use super::*; #[cfg(test)] @@ -339,7 +339,7 @@ mod remaining_contribute_extrinsic { // Can't contribute with only the evaluation bond inst.execute(|| { assert_noop!( - Pallet::::remaining_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BOB), get_mock_jwt_with_cid( BOB, @@ -361,7 +361,7 @@ mod remaining_contribute_extrinsic { inst.calculate_contributed_funding_asset_spent(vec![(BOB, usable_ct / 2).into()], ct_price); inst.mint_foreign_asset_to(contribution_usdt.clone()); inst.execute(|| { - assert_ok!(Pallet::::remaining_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BOB), get_mock_jwt_with_cid( BOB, @@ -381,7 +381,7 @@ mod remaining_contribute_extrinsic { inst.calculate_contributed_funding_asset_spent(vec![(CARL, usable_ct).into()], ct_price); inst.mint_foreign_asset_to(contribution_usdt.clone()); inst.execute(|| { - assert_ok!(Pallet::::remaining_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(CARL), get_mock_jwt_with_cid( CARL, @@ -443,9 +443,8 @@ mod remaining_contribute_extrinsic { inst.bid_for_users(project_id, vec![bob_bid]).unwrap(); inst.start_community_funding(project_id).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound); + assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); inst.start_remainder_or_end_funding(project_id).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::RemainderRound); let plmc_price = ::PriceProvider::get_decimals_aware_price( PLMC_FOREIGN_ID, @@ -462,7 +461,7 @@ mod remaining_contribute_extrinsic { let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![bob_contribution], wap); inst.mint_foreign_asset_to(contribution_usdt.clone()); inst.execute(|| { - assert_ok!(Pallet::::remaining_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(bob), get_mock_jwt_with_cid( bob, @@ -655,7 +654,7 @@ mod remaining_contribute_extrinsic { inst.mint_foreign_asset_to(necessary_usdt.clone()); } inst.execute(|| { - Pallet::::remaining_contribute( + Pallet::::contribute( RuntimeOrigin::signed(contributor), jwt, project_id, @@ -855,9 +854,8 @@ mod remaining_contribute_extrinsic { .unwrap(); inst.bid_for_users(project_id, failing_bids_after_random_end).unwrap(); inst.advance_time(2).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound); + assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); inst.start_remainder_or_end_funding(project_id).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::RemainderRound); // Some low amount of plmc and usdt to cover a purchase of 10CTs. let plmc_mints = vec![ @@ -885,7 +883,7 @@ mod remaining_contribute_extrinsic { let mut bid_should_succeed = |account, investor_type, did_acc| { inst.execute(|| { - assert_ok!(Pallet::::remaining_contribute( + assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(account), get_mock_jwt_with_cid( account, @@ -973,7 +971,7 @@ mod remaining_contribute_extrinsic { }); inst.execute(|| { - assert_ok!(PolimecFunding::remaining_contribute( + assert_ok!(PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_4), get_mock_jwt_with_cid( BUYER_4, @@ -1074,7 +1072,7 @@ mod remaining_contribute_extrinsic { }); inst.execute(|| { - assert_ok!(PolimecFunding::remaining_contribute( + assert_ok!(PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_4), get_mock_jwt_with_cid( BUYER_4, @@ -1304,7 +1302,7 @@ mod remaining_contribute_extrinsic { default_community_buys(), ); assert_err!( - inst.execute(|| crate::Pallet::::do_remaining_contribute( + inst.execute(|| crate::Pallet::::do_contribute( &(&ISSUER_1 + 1), project_id, 500 * CT_UNIT, @@ -1378,7 +1376,7 @@ mod remaining_contribute_extrinsic { ); inst.execute(|| { assert_noop!( - Pallet::::remaining_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_4), jwt, project_id, @@ -1398,7 +1396,7 @@ mod remaining_contribute_extrinsic { ); inst.execute(|| { assert_noop!( - Pallet::::remaining_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_5), jwt, project_id, @@ -1419,7 +1417,7 @@ mod remaining_contribute_extrinsic { ); inst.execute(|| { assert_noop!( - Pallet::::remaining_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_6), jwt, project_id, @@ -1491,7 +1489,7 @@ mod remaining_contribute_extrinsic { // total contributions with same DID above 30k CT (300k USD) should fail for retail inst.execute(|| { - assert_ok!(Pallet::::do_remaining_contribute( + assert_ok!(Pallet::::do_contribute( &BUYER_4, project_id, 28_000 * CT_UNIT, @@ -1504,7 +1502,7 @@ mod remaining_contribute_extrinsic { }); inst.execute(|| { assert_noop!( - Pallet::::do_remaining_contribute( + Pallet::::do_contribute( &BUYER_5, project_id, 2001 * CT_UNIT, @@ -1520,7 +1518,7 @@ mod remaining_contribute_extrinsic { }); // bidding 2k total works inst.execute(|| { - assert_ok!(Pallet::::do_remaining_contribute( + assert_ok!(Pallet::::do_contribute( &BUYER_5, project_id, 2000 * CT_UNIT, @@ -1535,7 +1533,7 @@ mod remaining_contribute_extrinsic { // total contributions with same DID above 2k CT (20k USD) should fail for professionals inst.execute(|| { - assert_ok!(Pallet::::do_remaining_contribute( + assert_ok!(Pallet::::do_contribute( &BUYER_6, project_id, 1800 * CT_UNIT, @@ -1548,7 +1546,7 @@ mod remaining_contribute_extrinsic { }); inst.execute(|| { assert_noop!( - Pallet::::do_remaining_contribute( + Pallet::::do_contribute( &BUYER_7, project_id, 201 * CT_UNIT, @@ -1564,7 +1562,7 @@ mod remaining_contribute_extrinsic { }); // bidding 2k total works inst.execute(|| { - assert_ok!(Pallet::::do_remaining_contribute( + assert_ok!(Pallet::::do_contribute( &BUYER_7, project_id, 200 * CT_UNIT, @@ -1579,7 +1577,7 @@ mod remaining_contribute_extrinsic { // total contributions with same DID above 5k CT (50 USD) should fail for institutionals inst.execute(|| { - assert_ok!(Pallet::::do_remaining_contribute( + assert_ok!(Pallet::::do_contribute( &BUYER_8, project_id, 4690 * CT_UNIT, @@ -1592,7 +1590,7 @@ mod remaining_contribute_extrinsic { }); inst.execute(|| { assert_noop!( - Pallet::::do_remaining_contribute( + Pallet::::do_contribute( &BUYER_9, project_id, 311 * CT_UNIT, @@ -1608,7 +1606,7 @@ mod remaining_contribute_extrinsic { }); // bidding 5k total works inst.execute(|| { - assert_ok!(Pallet::::do_remaining_contribute( + assert_ok!(Pallet::::do_contribute( &BUYER_9, project_id, 310 * CT_UNIT, @@ -1655,7 +1653,7 @@ mod remaining_contribute_extrinsic { inst.mint_foreign_asset_to(foreign_funding.clone()); inst.execute(|| { assert_noop!( - Pallet::::remaining_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_1), jwt.clone(), project_id, @@ -1690,7 +1688,7 @@ mod remaining_contribute_extrinsic { inst.execute(|| { assert_noop!( - Pallet::::remaining_contribute( + Pallet::::contribute( RuntimeOrigin::signed(BUYER_1), jwt, project_id, @@ -1717,7 +1715,7 @@ mod remaining_contribute_extrinsic { inst.execute(|| { assert_noop!( - PolimecFunding::remaining_contribute( + PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_1), get_mock_jwt_with_cid( BUYER_1, @@ -1850,7 +1848,7 @@ mod remaining_contribute_extrinsic { inst.execute(|| { assert_noop!( - PolimecFunding::remaining_contribute( + PolimecFunding::contribute( RuntimeOrigin::signed(evaluator_contributor), get_mock_jwt_with_cid( evaluator_contributor, @@ -1883,7 +1881,7 @@ mod remaining_contribute_extrinsic { inst.execute(|| { assert_noop!( - PolimecFunding::remaining_contribute( + PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_1), get_mock_jwt_with_cid( BUYER_1, diff --git a/pallets/funding/src/tests/6_funding_end.rs b/pallets/funding/src/tests/6_funding_end.rs index f8d9a8f6f..fb7a98fcc 100644 --- a/pallets/funding/src/tests/6_funding_end.rs +++ b/pallets/funding/src/tests/6_funding_end.rs @@ -349,7 +349,7 @@ mod decide_project_outcome { default_evaluations(), default_bids(), ); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound); + assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); call_fails(project_id, ISSUER_5, &mut inst); // Remainder @@ -361,7 +361,6 @@ mod decide_project_outcome { default_bids(), vec![], ); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::RemainderRound); call_fails(project_id, ISSUER_6, &mut inst); // FundingSuccessful diff --git a/pallets/funding/src/tests/7_settlement.rs b/pallets/funding/src/tests/7_settlement.rs index 66a8afd60..f35835ea5 100644 --- a/pallets/funding/src/tests/7_settlement.rs +++ b/pallets/funding/src/tests/7_settlement.rs @@ -693,7 +693,7 @@ mod settle_successful_contribution_extrinsic { inst.mint_foreign_asset_to(usdt_required.clone()); inst.execute(|| { - assert_ok!(PolimecFunding::remaining_contribute( + assert_ok!(PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_7), get_mock_jwt_with_cid( BUYER_7, @@ -1326,7 +1326,7 @@ mod settle_failed_contribution_extrinsic { inst.mint_foreign_asset_to(usdt_required.clone()); inst.execute(|| { - assert_ok!(PolimecFunding::remaining_contribute( + assert_ok!(PolimecFunding::contribute( RuntimeOrigin::signed(BUYER_7), get_mock_jwt_with_cid( BUYER_7, diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 0fb050050..daf862b02 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -326,7 +326,7 @@ pub mod storage_types { /// The price in USD per token decided after the Auction Round pub weighted_average_price: Option, /// The current status of the project - pub status: ProjectStatus, + pub status: ProjectStatus, /// When the different project phases start and end pub round_duration: BlockNumberPair, /// Random block end for auction round @@ -693,14 +693,13 @@ pub mod inner_types { #[derive( Default, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, Serialize, Deserialize, )] - pub enum ProjectStatus { + pub enum ProjectStatus { #[default] Application, EvaluationRound, AuctionInitializePeriod, Auction, - CommunityRound, - RemainderRound, + CommunityRound(BlockNumber), FundingFailed, AwaitingProjectDecision, FundingSuccessful, diff --git a/pallets/sandbox/src/lib.rs b/pallets/sandbox/src/lib.rs index fbecdaaa6..6d81d82a0 100644 --- a/pallets/sandbox/src/lib.rs +++ b/pallets/sandbox/src/lib.rs @@ -77,7 +77,7 @@ pub mod pallet { let multiplier: MultiplierOf = 1u8.try_into().map_err(|_| Error::::ProjectNotFound)?; // Buy tokens with the default multiplier - >::do_community_contribute( + >::do_contribute( &retail_user, project_id, amount, diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 549ad3696..308b62df0 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -248,11 +248,9 @@ impl Contains for BaseCallFilter { pallet_funding::Call::root_do_start_auction_closing { .. } | pallet_funding::Call::bid { .. } | pallet_funding::Call::root_do_end_auction { .. } | - pallet_funding::Call::community_contribute { .. } | - pallet_funding::Call::remaining_contribute { .. } | + pallet_funding::Call::contribute { .. } | pallet_funding::Call::decide_project_outcome { .. } | pallet_funding::Call::root_do_community_funding { .. } | - pallet_funding::Call::root_do_remainder_funding { .. } | pallet_funding::Call::root_do_end_funding { .. } | pallet_funding::Call::root_do_project_decision { .. } | pallet_funding::Call::root_do_start_settlement { .. } | From 642f100c893ab4fb0466b06adf3aaa1a13abe0e0 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 15 Jul 2024 14:59:21 +0200 Subject: [PATCH 4/7] :recycle: remove project decision and add success threshold + improve reward info calculation --- pallets/funding/src/benchmarking.rs | 5 - .../funding/src/functions/5_funding_end.rs | 148 ++------- pallets/funding/src/functions/6_settlement.rs | 2 + pallets/funding/src/functions/misc.rs | 108 +++---- pallets/funding/src/lib.rs | 31 -- pallets/funding/src/tests/4_community.rs | 12 - pallets/funding/src/tests/5_remainder.rs | 13 - pallets/funding/src/tests/6_funding_end.rs | 280 ------------------ pallets/funding/src/tests/mod.rs | 12 +- pallets/funding/src/types.rs | 10 +- runtimes/polimec/src/benchmarks/mod.rs | 4 - runtimes/polimec/src/lib.rs | 2 - 12 files changed, 76 insertions(+), 551 deletions(-) diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index cd88c07cf..67579990c 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -1435,11 +1435,6 @@ mod benchmarks { let maybe_transition = inst.get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)); assert!(maybe_transition.is_some()); - - // Events - frame_system::Pallet::::assert_last_event( - Event::ProjectOutcomeDecided { project_id, decision: FundingOutcomeDecision::AcceptFunding }.into(), - ); } #[benchmark] diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/5_funding_end.rs index 7f23fa41f..5adb403cc 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -36,7 +36,7 @@ impl Pallet { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let remaining_cts = project_details.remaining_contribution_tokens; - let remainder_end_block = project_details.round_duration.end(); + let round_end_block = project_details.round_duration.end().ok_or(Error::::ImpossibleState)?; let now = >::block_number(); let issuer_did = project_details.issuer_did.clone(); @@ -45,140 +45,50 @@ impl Pallet { // Can end due to running out of CTs remaining_cts == Zero::zero() || // or the last funding round ending - matches!(remainder_end_block, Some(end_block) if now > end_block), + now > round_end_block && matches!(project_details.status, ProjectStatus::CommunityRound(..)), Error::::TooEarlyForRound ); - // do_end_funding was already executed, but automatic transition was included in the - // do_remainder_funding function. We gracefully skip the this transition. - ensure!( - !matches!( - project_details.status, - ProjectStatus::FundingSuccessful | - ProjectStatus::FundingFailed - ), - Error::::RoundTransitionAlreadyHappened - ); // * Calculate new variables * - let funding_target = project_metadata - .minimum_price - .checked_mul_int(project_metadata.total_allocation_size) - .ok_or(Error::::BadMath)?; + let funding_target = project_details.fundraising_target_usd; let funding_reached = project_details.funding_amount_reached_usd; let funding_ratio = Perquintill::from_rational(funding_reached, funding_target); // * Update Storage * DidWithActiveProjects::::set(issuer_did, None); - if funding_ratio <= Perquintill::from_percent(33u64) { - project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed; - let insertion_iterations = - Self::finalize_funding(project_id, project_details, ProjectOutcome::FundingFailed, 1u32.into())?; - return Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::end_funding_automatically_rejected_evaluators_slashed( - insertion_iterations, - )), - pays_fee: Pays::Yes, - }); - } else if funding_ratio <= Perquintill::from_percent(75u64) { - project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed; - project_details.status = ProjectStatus::AwaitingProjectDecision; - - ProjectsDetails::::insert(project_id, project_details); - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::end_funding_awaiting_decision_evaluators_slashed( - 1, - )), - pays_fee: Pays::Yes, - }) - } else if funding_ratio < Perquintill::from_percent(90u64) { - project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Unchanged; - project_details.status = ProjectStatus::AwaitingProjectDecision; - ProjectsDetails::::insert(project_id, project_details); - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::end_funding_awaiting_decision_evaluators_unchanged( - 1, - )), - pays_fee: Pays::Yes, - }) - } else { - let (reward_info, evaluations_count) = Self::generate_evaluator_rewards_info(project_id)?; - project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Rewarded(reward_info); - - Self::finalize_funding( - project_id, - project_details, - ProjectOutcome::FundingSuccessful, - T::SuccessToSettlementTime::get(), - )?; - return Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::end_funding_automatically_accepted_evaluators_rewarded( - 1, - evaluations_count, - )), - pays_fee: Pays::Yes, - }); - } - } - - #[transactional] - pub fn do_project_decision(project_id: ProjectId, decision: FundingOutcomeDecision) -> DispatchResultWithPostInfo { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - ensure!( - project_details.status == ProjectStatus::AwaitingProjectDecision, - Error::::RoundTransitionAlreadyHappened - ); - let outcome = match decision { - FundingOutcomeDecision::AcceptFunding => ProjectOutcome::FundingAccepted, - FundingOutcomeDecision::RejectFunding => ProjectOutcome::FundingRejected, + let evaluator_outcome = match funding_ratio { + ratio if ratio <= Perquintill::from_percent(75u64) => EvaluatorsOutcome::Slashed, + ratio if ratio < Perquintill::from_percent(90u64) => EvaluatorsOutcome::Unchanged, + _ => { + let reward_info = Self::generate_evaluator_rewards_info(project_id)?; + EvaluatorsOutcome::Rewarded(reward_info) + }, }; - // * Update storage * - Self::finalize_funding(project_id, project_details, outcome, T::SuccessToSettlementTime::get())?; - Ok(PostDispatchInfo { actual_weight: Some(WeightInfoOf::::project_decision()), pays_fee: Pays::Yes }) - } - - #[transactional] - pub fn do_decide_project_outcome( - issuer: AccountIdOf, - project_id: ProjectId, - decision: FundingOutcomeDecision, - ) -> DispatchResultWithPostInfo { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let now = >::block_number(); - - // * Validity checks * - ensure!(project_details.issuer_account == issuer, Error::::NotIssuer); - ensure!(project_details.status == ProjectStatus::AwaitingProjectDecision, Error::::IncorrectRound); + project_details.evaluation_round_info.evaluators_outcome = evaluator_outcome; + + let (next_status, duration, actual_weight) = if funding_ratio <= T::FundingSuccessThreshold::get() { + ( + ProjectStatus::FundingFailed, + 1u32.into(), + WeightInfoOf::::end_funding_automatically_rejected_evaluators_slashed(1) + ) + } else { + ( + ProjectStatus::FundingSuccessful, + T::SuccessToSettlementTime::get(), + WeightInfoOf::::end_funding_automatically_accepted_evaluators_rewarded(1,1) + ) + }; - // * Update storage * - Self::deposit_event(Event::ProjectOutcomeDecided { project_id, decision }); + let round_end = now.saturating_add(duration).saturating_sub(One::one()); + project_details.round_duration.update(Some(now), Some(round_end)); + project_details.status = next_status; Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::decide_project_outcome(1)), + actual_weight: Some(actual_weight), pays_fee: Pays::Yes, }) } - pub fn finalize_funding( - project_id: ProjectId, - mut project_details: ProjectDetailsOf, - outcome: ProjectOutcome, - settlement_delta: BlockNumberFor, - ) -> Result { - let now = >::block_number(); - - project_details.status = match outcome { - ProjectOutcome::FundingSuccessful | ProjectOutcome::FundingAccepted => ProjectStatus::FundingSuccessful, - _ => ProjectStatus::FundingFailed, - }; - ProjectsDetails::::insert(project_id, project_details); - - Self::deposit_event(Event::ProjectPhaseTransition { - project_id, - phase: ProjectPhases::FundingFinalization(outcome), - }); - Ok(1) - } } diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index 0d1ddea33..af6168021 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -27,6 +27,7 @@ impl Pallet { let token_information = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?.token_information; let now = >::block_number(); + let round_end_block = project_details.round_duration.end().ok_or(Error::::ImpossibleState)?; // * Validity checks * ensure!( @@ -34,6 +35,7 @@ impl Pallet { project_details.status == ProjectStatus::FundingFailed, Error::::IncorrectRound ); + ensure!(now > round_end_block, Error::::TooEarlyForRound); // * Calculate new variables * project_details.funding_end_block = Some(now); diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index b988ff573..ab14ffaba 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -184,14 +184,34 @@ impl Pallet { Ok(()) } - /// Calculate the total fees based on the funding reached. - pub fn calculate_fees(funding_reached: BalanceOf) -> Perquintill { - let total_fee = Self::compute_total_fee_from_brackets(funding_reached); - Perquintill::from_rational(total_fee, funding_reached) + // Calculate the total fee allocation for a project, based on the funding reached. + fn calculate_fee_allocation(project_id: ProjectId) -> Result, DispatchError> { + let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; + + // Fetching the necessary data for a specific project. + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + + // Determine how much funding has been achieved. + let funding_amount_reached = project_details.funding_amount_reached_usd; + let fundraising_target = project_details.fundraising_target_usd; + let fee_usd = Self::compute_total_fee_from_brackets(funding_amount_reached); + let fee_percentage = Perquintill::from_rational(fee_usd, funding_amount_reached); + + let initial_token_allocation_size = project_metadata.total_allocation_size; + let final_remaining_contribution_tokens = project_details.remaining_contribution_tokens; + + // Calculate the number of tokens sold for the project. + let token_sold = initial_token_allocation_size + .checked_sub(&final_remaining_contribution_tokens) + // Ensure safety by providing a default in case of unexpected situations. + .unwrap_or(initial_token_allocation_size); + let total_fee_allocation = fee_percentage * token_sold; + + Ok(total_fee_allocation) } /// Computes the total fee from all defined fee brackets. - pub fn compute_total_fee_from_brackets(funding_reached: BalanceOf) -> BalanceOf { + fn compute_total_fee_from_brackets(funding_reached: BalanceOf) -> BalanceOf { let mut remaining_for_fee = funding_reached; T::FeeBrackets::get() @@ -201,7 +221,7 @@ impl Pallet { } /// Calculate the fee for a particular bracket. - pub fn compute_fee_for_bracket( + fn compute_fee_for_bracket( remaining_for_fee: &mut BalanceOf, fee: Percent, limit: BalanceOf, @@ -224,33 +244,15 @@ impl Pallet { /// /// Note: Consider refactoring the `RewardInfo` struct to make it more generic and /// reusable, not just for evaluator rewards. - pub fn generate_evaluator_rewards_info(project_id: ProjectId) -> Result<(RewardInfoOf, u32), DispatchError> { + pub fn generate_evaluator_rewards_info(project_id: ProjectId) -> Result, DispatchError> { // Fetching the necessary data for a specific project. let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let evaluations = Evaluations::::iter_prefix((project_id,)).collect::>(); - // used for weight calculation - let evaluations_count = evaluations.len() as u32; + let total_fee_allocation = Self::calculate_fee_allocation(project_id)?; - // Determine how much funding has been achieved. - let funding_amount_reached = project_details.funding_amount_reached_usd; - let fundraising_target = project_details.fundraising_target_usd; - let total_issuer_fees = Self::calculate_fees(funding_amount_reached); - - let initial_token_allocation_size = project_metadata.total_allocation_size; - let final_remaining_contribution_tokens = project_details.remaining_contribution_tokens; - - // Calculate the number of tokens sold for the project. - let token_sold = initial_token_allocation_size - .checked_sub(&final_remaining_contribution_tokens) - // Ensure safety by providing a default in case of unexpected situations. - .unwrap_or(initial_token_allocation_size); - let total_fee_allocation = total_issuer_fees * token_sold; - - // Calculate the percentage of target funding based on available documentation. - // A.K.A variable "Y" in the documentation. We mean it to saturate to 1 even if the ratio is above 1 when funding raised - // is above the target. - let percentage_of_target_funding = Perquintill::from_rational(funding_amount_reached, fundraising_target); + // Calculate the percentage of target funding based on available documentation. + // A.K.A variable "Y" in the documentation. We mean it to saturate to 1 even if the ratio is above 1 when funding raised + // is above the target. + let percentage_of_target_funding = Perquintill::from_rational(project_details.funding_amount_reached_usd, project_details.fundraising_target_usd); // Calculate rewards. let evaluator_rewards = percentage_of_target_funding * Perquintill::from_percent(30) * total_fee_allocation; @@ -259,18 +261,10 @@ impl Pallet { let early_evaluator_reward_pot = Perquintill::from_percent(20) * evaluator_rewards; let normal_evaluator_reward_pot = Perquintill::from_percent(80) * evaluator_rewards; - // Sum up the total bonded USD amounts for both early and late evaluators. - let early_evaluator_total_bonded_usd = - evaluations.iter().fold(BalanceOf::::zero(), |acc, ((_evaluator, _id), evaluation)| { - acc.saturating_add(evaluation.early_usd_amount) - }); - let late_evaluator_total_bonded_usd = - evaluations.iter().fold(BalanceOf::::zero(), |acc, ((_evaluator, _id), evaluation)| { - acc.saturating_add(evaluation.late_usd_amount) - }); - - let normal_evaluator_total_bonded_usd = - early_evaluator_total_bonded_usd.saturating_add(late_evaluator_total_bonded_usd); + let normal_evaluator_total_bonded_usd = project_details.evaluation_round_info.total_bonded_usd; + let early_evaluation_reward_threshold_usd = + T::EvaluationSuccessThreshold::get() * project_details.fundraising_target_usd; + let early_evaluator_total_bonded_usd = normal_evaluator_total_bonded_usd.min(early_evaluation_reward_threshold_usd); // Construct the reward information object. let reward_info = RewardInfo { @@ -280,35 +274,19 @@ impl Pallet { normal_evaluator_total_bonded_usd, }; - Ok((reward_info, evaluations_count)) + Ok(reward_info) } pub fn generate_liquidity_pools_and_long_term_holder_rewards( project_id: ProjectId, ) -> Result<(BalanceOf, BalanceOf), DispatchError> { - // Fetching the necessary data for a specific project. - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - - // Determine how much funding has been achieved. - let funding_amount_reached = project_details.funding_amount_reached_usd; - let fundraising_target = project_details.fundraising_target_usd; - let total_issuer_fees = Self::calculate_fees(funding_amount_reached); - - let initial_token_allocation_size = project_metadata.total_allocation_size; - let final_remaining_contribution_tokens = project_details.remaining_contribution_tokens; - - // Calculate the number of tokens sold for the project. - let token_sold = initial_token_allocation_size - .checked_sub(&final_remaining_contribution_tokens) - // Ensure safety by providing a default in case of unexpected situations. - .unwrap_or(initial_token_allocation_size); - let total_fee_allocation = total_issuer_fees * token_sold; + let details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let total_fee_allocation = Self::calculate_fee_allocation(project_id)?; - // Calculate the percentage of target funding based on available documentation. - // A.K.A variable "Y" in the documentation. We mean it to saturate to 1 even if the ratio is above 1 when funding raised - // is above the target. - let percentage_of_target_funding = Perquintill::from_rational(funding_amount_reached, fundraising_target); + // Calculate the percentage of target funding based on available documentation. + // A.K.A variable "Y" in the documentation. We mean it to saturate to 1 even if the ratio is above 1 when funding raised + // is above the target. + let percentage_of_target_funding = Perquintill::from_rational(details.funding_amount_reached_usd, details.fundraising_target_usd); let inverse_percentage_of_target_funding = Perquintill::from_percent(100) - percentage_of_target_funding; let liquidity_pools_percentage = Perquintill::from_percent(50); diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index e9aaa6b2f..976938625 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -610,10 +610,6 @@ pub mod pallet { plmc_bond: BalanceOf, multiplier: MultiplierOf, }, - ProjectOutcomeDecided { - project_id: ProjectId, - decision: FundingOutcomeDecision, - }, BidRefunded { project_id: ProjectId, account: AccountIdOf, @@ -1010,33 +1006,6 @@ pub mod pallet { Self::do_end_funding(project_id) } - #[pallet::call_index(16)] - #[pallet::weight(WeightInfoOf::::decide_project_outcome( - 1 - ))] - pub fn decide_project_outcome( - origin: OriginFor, - jwt: UntrustedToken, - project_id: ProjectId, - outcome: FundingOutcomeDecision, - ) -> DispatchResultWithPostInfo { - let (account, _did, investor_type, _cid) = - T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; - ensure!(investor_type == InvestorType::Institutional, Error::::WrongInvestorType); - - Self::do_decide_project_outcome(account, project_id, outcome) - } - - #[pallet::call_index(17)] - #[pallet::weight(WeightInfoOf::::project_decision())] - pub fn root_do_project_decision( - origin: OriginFor, - project_id: ProjectId, - decision: FundingOutcomeDecision, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - Self::do_project_decision(project_id, decision) - } #[pallet::call_index(18)] #[pallet::weight(WeightInfoOf::::start_settlement_funding_success() diff --git a/pallets/funding/src/tests/4_community.rs b/pallets/funding/src/tests/4_community.rs index 49026404e..eedc4fd9d 100644 --- a/pallets/funding/src/tests/4_community.rs +++ b/pallets/funding/src/tests/4_community.rs @@ -1000,18 +1000,6 @@ mod contribute_extrinsic { inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); - inst.execute(|| { - assert_ok!(PolimecFunding::decide_project_outcome( - RuntimeOrigin::signed(ISSUER_1), - get_mock_jwt(ISSUER_1, InvestorType::Institutional, generate_did_from_account(ISSUER_1)), - project_id, - FundingOutcomeDecision::AcceptFunding - )); - }); - let decision_block = inst - .get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)) - .unwrap(); - inst.jump_to_block(decision_block); let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); inst.jump_to_block(settlement_block); diff --git a/pallets/funding/src/tests/5_remainder.rs b/pallets/funding/src/tests/5_remainder.rs index c44f5e324..cac2595f5 100644 --- a/pallets/funding/src/tests/5_remainder.rs +++ b/pallets/funding/src/tests/5_remainder.rs @@ -1090,19 +1090,6 @@ mod contribute_extrinsic { inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); - inst.execute(|| { - assert_ok!(PolimecFunding::decide_project_outcome( - RuntimeOrigin::signed(ISSUER_1), - get_mock_jwt_with_cid( - ISSUER_1, - InvestorType::Institutional, - generate_did_from_account(ISSUER_1), - project_metadata.policy_ipfs_cid.unwrap() - ), - project_id, - FundingOutcomeDecision::AcceptFunding - )); - }); let decision_block = inst .get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)) .unwrap(); diff --git a/pallets/funding/src/tests/6_funding_end.rs b/pallets/funding/src/tests/6_funding_end.rs index fb7a98fcc..1754dd2e6 100644 --- a/pallets/funding/src/tests/6_funding_end.rs +++ b/pallets/funding/src/tests/6_funding_end.rs @@ -111,283 +111,3 @@ mod round_flow { } } } - -#[cfg(test)] -mod decide_project_outcome { - use super::*; - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn manual_acceptance_percentage_between_34_89() { - for funding_percent in (34..=89).step_by(5) { - let _ = create_project_with_funding_percentage( - funding_percent, - Some(FundingOutcomeDecision::AcceptFunding), - true, - ); - } - } - - #[test] - fn manual_rejection_percentage_between_34_89() { - for funding_percent in (34..=89).step_by(5) { - let _ = create_project_with_funding_percentage( - funding_percent, - Some(FundingOutcomeDecision::RejectFunding), - true, - ); - } - } - - #[test] - fn automatic_fail_less_eq_33_percent() { - for funding_percent in (1..=33).step_by(5) { - let _ = create_project_with_funding_percentage(funding_percent, None, true); - } - } - - #[test] - fn automatic_acceptance_on_manual_decision_after_time_delta() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let min_price = project_metadata.minimum_price; - let twenty_percent_funding_usd = Perquintill::from_percent(55) * - (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); - let evaluations = default_evaluations(); - let bids = inst.generate_bids_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let contributions = inst.generate_contributions_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_community_contributors(), - default_multipliers(), - ); - let project_id = inst.create_finished_project( - project_metadata, - ISSUER_1, - None, - evaluations, - bids, - contributions, - vec![], - ); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); - - inst.advance_time(1u64 + ::ManualAcceptanceDuration::get()).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - - inst.test_ct_created_for(project_id); - - inst.settle_project(project_id).unwrap(); - } - - #[test] - fn automatic_success_bigger_eq_90_percent() { - for funding_percent in (90..=100).step_by(2) { - let _ = create_project_with_funding_percentage(funding_percent, None, true); - } - } - } - - #[cfg(test)] - mod failure { - use super::*; - - #[test] - fn called_by_non_issuer() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let funding_percentage = 40u64; - let min_price = project_metadata.minimum_price; - let percentage_funded_usd = Perquintill::from_percent(funding_percentage) * - (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); - let evaluations = default_evaluations(); - let bids = inst.generate_bids_from_total_usd( - Percent::from_percent(50u8) * percentage_funded_usd, - min_price, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let contributions = inst.generate_contributions_from_total_usd( - Percent::from_percent(50u8) * percentage_funded_usd, - min_price, - default_weights(), - default_community_contributors(), - default_multipliers(), - ); - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations, - bids, - contributions, - vec![], - ); - - inst.execute(|| { - // Accepting doesn't work - assert_noop!( - PolimecFunding::decide_project_outcome( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Institutional, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - project_id, - FundingOutcomeDecision::AcceptFunding - ), - Error::::NotIssuer - ); - // Rejecting doesn't work - assert_noop!( - PolimecFunding::decide_project_outcome( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Institutional, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - project_id, - FundingOutcomeDecision::AcceptFunding - ), - Error::::NotIssuer - ); - - // But Issuer can accept or reject - assert_ok!(PolimecFunding::decide_project_outcome( - RuntimeOrigin::signed(ISSUER_1), - get_mock_jwt_with_cid( - ISSUER_1, - InvestorType::Institutional, - generate_did_from_account(ISSUER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - project_id, - FundingOutcomeDecision::AcceptFunding - )); - }) - } - - #[test] - fn called_on_incorrect_project_status() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - - let call_fails = |project_id, issuer, inst: &mut MockInstantiator| { - let jwt = |issuer| { - get_mock_jwt_with_cid( - issuer, - InvestorType::Institutional, - generate_did_from_account(issuer), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ) - }; - - inst.execute(|| { - assert_noop!( - PolimecFunding::decide_project_outcome( - RuntimeOrigin::signed(issuer), - jwt(issuer), - project_id, - FundingOutcomeDecision::AcceptFunding - ), - Error::::IncorrectRound - ); - assert_noop!( - PolimecFunding::decide_project_outcome( - RuntimeOrigin::signed(issuer), - jwt(issuer), - project_id, - FundingOutcomeDecision::RejectFunding - ), - Error::::IncorrectRound - ); - }); - }; - - // Application - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); - call_fails(project_id, ISSUER_1, &mut inst); - - // Evaluation - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2, None); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationRound); - call_fails(project_id, ISSUER_2, &mut inst); - - // EvaluationFailed - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_3, None); - let transition_block = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); - inst.jump_to_block(transition_block); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - call_fails(project_id, ISSUER_3, &mut inst); - - // Auction - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_4, default_evaluations()); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); - call_fails(project_id, ISSUER_4, &mut inst); - - // Community - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_5, - None, - default_evaluations(), - default_bids(), - ); - assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); - call_fails(project_id, ISSUER_5, &mut inst); - - // Remainder - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_6, - None, - default_evaluations(), - default_bids(), - vec![], - ); - call_fails(project_id, ISSUER_6, &mut inst); - - // FundingSuccessful - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_7, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - default_remainder_buys(), - ); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - call_fails(project_id, ISSUER_7, &mut inst); - - // FundingFailed - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_8, - None, - default_evaluations(), - vec![default_bids()[1].clone()], - vec![], - vec![], - ); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - call_fails(project_id, ISSUER_8, &mut inst); - } - } -} diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index 2913b986f..216864deb 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -398,18 +398,8 @@ pub fn create_project_with_funding_percentage( inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]); match inst.get_project_details(project_id).status { - ProjectStatus::AwaitingProjectDecision => { - assert!(percentage > 33 && percentage < 90); - assert!(maybe_decision.is_some()); - inst.execute(|| PolimecFunding::do_decide_project_outcome(ISSUER_1, project_id, maybe_decision.unwrap())) - .unwrap(); - - let decision_execution = - inst.get_update_block(project_id, &UpdateType::ProjectDecision(maybe_decision.unwrap())).unwrap(); - inst.jump_to_block(decision_execution); - }, ProjectStatus::FundingSuccessful => { - assert!(percentage >= 90); + assert!(percentage >= 33); }, ProjectStatus::FundingFailed => { assert!(percentage <= 33); diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index daf862b02..b0e78dbc3 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -786,9 +786,7 @@ pub mod inner_types { AuctionInitializePeriod, Auction, CommunityFunding, - RemainderFunding, - DecisionPeriod, - FundingFinalization(ProjectOutcome), + FundingFinalization, Settlement, Migration, } @@ -796,16 +794,10 @@ pub mod inner_types { /// An enum representing all possible outcomes for a project. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum ProjectOutcome { - /// The evaluation funding target was not reached. - EvaluationFailed, /// 90%+ of the funding target was reached, so the project is successful. FundingSuccessful, /// 33%- of the funding target was reached, so the project failed. FundingFailed, - /// The project issuer accepted the funding outcome between 33% and 90% of the target. - FundingAccepted, - /// The project issuer rejected the funding outcome between 33% and 90% of the target. - FundingRejected, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] diff --git a/runtimes/polimec/src/benchmarks/mod.rs b/runtimes/polimec/src/benchmarks/mod.rs index a07674817..8c7349dd1 100644 --- a/runtimes/polimec/src/benchmarks/mod.rs +++ b/runtimes/polimec/src/benchmarks/mod.rs @@ -46,10 +46,6 @@ fn output_max_pallet_funding_values() { ); dbg!(contribution_ends_round); - let decide_project_outcome = - SubstrateWeight::::decide_project_outcome(1); - dbg!(decide_project_outcome); - let settle_successful_evaluation = SubstrateWeight::::settle_successful_evaluation(); dbg!(settle_successful_evaluation); diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 308b62df0..5c12e881e 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -249,10 +249,8 @@ impl Contains for BaseCallFilter { pallet_funding::Call::bid { .. } | pallet_funding::Call::root_do_end_auction { .. } | pallet_funding::Call::contribute { .. } | - pallet_funding::Call::decide_project_outcome { .. } | pallet_funding::Call::root_do_community_funding { .. } | pallet_funding::Call::root_do_end_funding { .. } | - pallet_funding::Call::root_do_project_decision { .. } | pallet_funding::Call::root_do_start_settlement { .. } | pallet_funding::Call::settle_successful_evaluation { .. } | pallet_funding::Call::settle_failed_evaluation { .. } | From 28971d2727494d946735b80203e795e7fce1f9cd Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 15 Jul 2024 15:00:00 +0200 Subject: [PATCH 5/7] :recycle: small additional decesion cleanup --- pallets/funding/src/benchmarking.rs | 5 +---- pallets/funding/src/instantiator/chain_interactions.rs | 3 +-- pallets/funding/src/lib.rs | 6 ------ pallets/funding/src/tests/4_community.rs | 2 -- pallets/funding/src/tests/5_remainder.rs | 1 - pallets/funding/src/types.rs | 1 - 6 files changed, 2 insertions(+), 16 deletions(-) diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 67579990c..0e1e4f8de 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -2428,7 +2428,7 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.status, ProjectStatus::AwaitingProjectDecision); + assert_eq!(project_details.evaluation_round_info.evaluators_outcome, EvaluatorsOutcome::Slashed) } #[benchmark] @@ -2487,7 +2487,6 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.status, ProjectStatus::AwaitingProjectDecision); assert_eq!(project_details.evaluation_round_info.evaluators_outcome, EvaluatorsOutcome::Unchanged) } #[benchmark] @@ -2606,8 +2605,6 @@ mod benchmarks { vec![], ); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); - #[block] { Pallet::::do_project_decision(project_id, FundingOutcomeDecision::AcceptFunding).unwrap(); diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 4d38b9d3c..77fd70baf 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -693,8 +693,7 @@ impl< matches!( project_details.status, ProjectStatus::FundingSuccessful | - ProjectStatus::FundingFailed | - ProjectStatus::AwaitingProjectDecision + ProjectStatus::FundingFailed ), "Project should be in Finished status" ); diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 976938625..3e27070e6 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -991,12 +991,6 @@ pub mod pallet { #[pallet::weight(WeightInfoOf::::end_funding_automatically_rejected_evaluators_slashed( 1, ) - .max(WeightInfoOf::::end_funding_awaiting_decision_evaluators_slashed( - 1, - )) - .max(WeightInfoOf::::end_funding_awaiting_decision_evaluators_unchanged( - 1, - )) .max(WeightInfoOf::::end_funding_automatically_accepted_evaluators_rewarded( 1, ::MaxEvaluationsPerProject::get(), diff --git a/pallets/funding/src/tests/4_community.rs b/pallets/funding/src/tests/4_community.rs index eedc4fd9d..b3ded28f6 100644 --- a/pallets/funding/src/tests/4_community.rs +++ b/pallets/funding/src/tests/4_community.rs @@ -999,8 +999,6 @@ mod contribute_extrinsic { inst.start_remainder_or_end_funding(project_id).unwrap(); inst.finish_funding(project_id, None).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); inst.jump_to_block(settlement_block); diff --git a/pallets/funding/src/tests/5_remainder.rs b/pallets/funding/src/tests/5_remainder.rs index cac2595f5..13f13f6e8 100644 --- a/pallets/funding/src/tests/5_remainder.rs +++ b/pallets/funding/src/tests/5_remainder.rs @@ -1089,7 +1089,6 @@ mod contribute_extrinsic { inst.finish_funding(project_id, None).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); let decision_block = inst .get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)) .unwrap(); diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index b0e78dbc3..07e4531d8 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -701,7 +701,6 @@ pub mod inner_types { Auction, CommunityRound(BlockNumber), FundingFailed, - AwaitingProjectDecision, FundingSuccessful, SettlementStarted(FundingOutcome), SettlementFinished(FundingOutcome), From 757ec6c7c1af5e49382304e2a6b798ab8aa98127 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Fri, 19 Jul 2024 13:24:04 +0200 Subject: [PATCH 6/7] fix most warnings --- pallets/funding/src/functions/2_evaluation.rs | 2 +- pallets/funding/src/functions/3_auction.rs | 14 ++--------- .../funding/src/functions/4_contribution.rs | 3 ++- .../funding/src/functions/5_funding_end.rs | 1 - pallets/funding/src/functions/misc.rs | 5 ++-- .../src/instantiator/chain_interactions.rs | 1 - pallets/funding/src/lib.rs | 25 +++---------------- pallets/funding/src/types.rs | 2 +- 8 files changed, 11 insertions(+), 42 deletions(-) diff --git a/pallets/funding/src/functions/2_evaluation.rs b/pallets/funding/src/functions/2_evaluation.rs index 9b7ee3d89..aeb8ea11b 100644 --- a/pallets/funding/src/functions/2_evaluation.rs +++ b/pallets/funding/src/functions/2_evaluation.rs @@ -73,7 +73,7 @@ impl Pallet { #[transactional] pub fn do_evaluation_end(project_id: ProjectId) -> DispatchResult { // * Get variables * - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; // * Calculate new variables * let usd_total_amount_bonded = project_details.evaluation_round_info.total_bonded_usd; diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index 398a8aa3c..69134c167 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -45,7 +45,6 @@ impl Pallet { #[transactional] pub fn do_end_auction(project_id: ProjectId) -> DispatchResultWithPostInfo { // * Get variables * - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; @@ -240,19 +239,10 @@ impl Pallet { ensure!(total_bids_by_bidder < T::MaxBidsPerUser::get(), Error::::TooManyUserParticipations); ensure!(total_bids_for_project < T::MaxBidsPerProject::get(), Error::::TooManyProjectParticipations); - let funding_asset_id = funding_asset.to_assethub_id(); - let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id); - let funding_asset_usd_price = - T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) - .ok_or(Error::::PriceNotFound)?; - // * Calculate new variables * let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier).map_err(|_| Error::::BadMath)?; - - 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_assethub_id(); + let funding_asset_amount_locked = Self::calculate_funding_asset_amount(ticket_size, funding_asset)?; let new_bid = BidInfoOf:: { id: bid_id, @@ -272,7 +262,7 @@ impl Pallet { }; Self::try_plmc_participation_lock(bidder, project_id, plmc_bond)?; - Self::try_funding_asset_hold(bidder, project_id, funding_asset_amount_locked, asset_id)?; + Self::try_funding_asset_hold(bidder, project_id, funding_asset_amount_locked, funding_asset.to_assethub_id())?; Bids::::insert((project_id, bidder, bid_id), &new_bid); NextBidId::::set(bid_id.saturating_add(One::one())); diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs index 6dd1c7b70..c71bb3309 100644 --- a/pallets/funding/src/functions/4_contribution.rs +++ b/pallets/funding/src/functions/4_contribution.rs @@ -24,7 +24,6 @@ impl Pallet { ) -> DispatchResultWithPostInfo { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let did_has_winning_bid = DidWithWinningBids::::get(project_id, did.clone()); - let round_start_block = project_details.round_duration.start().ok_or(Error::::ImpossibleState)?; let remainder_start = match project_details.status { ProjectStatus::CommunityRound(remainder_start) => remainder_start, @@ -33,7 +32,9 @@ impl Pallet { let now = >::block_number(); let remainder_started = now > remainder_start; + let round_end = project_details.round_duration.end().ok_or(Error::::ImpossibleState)?; ensure!(!did_has_winning_bid || remainder_started, Error::::UserHasWinningBid); + ensure!(now < round_end, Error::::TooLateForRound); let buyable_tokens = token_amount.min(project_details.remaining_contribution_tokens); project_details.remaining_contribution_tokens.saturating_reduce(buyable_tokens); diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/5_funding_end.rs index 5adb403cc..e4e47b8e2 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -34,7 +34,6 @@ impl Pallet { pub fn do_end_funding(project_id: ProjectId) -> DispatchResultWithPostInfo { // * Get variables * let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let remaining_cts = project_details.remaining_contribution_tokens; let round_end_block = project_details.round_duration.end().ok_or(Error::::ImpossibleState)?; let now = >::block_number(); diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index ab14ffaba..42343b88f 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -113,7 +113,7 @@ impl Pallet { .partition(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))); let accepted_bid_len = accepted_bids.len() as u32; - let mut total_auction_allocation_usd: BalanceOf = accepted_bids.into_iter() + let total_auction_allocation_usd: BalanceOf = accepted_bids.into_iter() .try_fold(Zero::zero(), |acc: BalanceOf, bid: BidInfoOf| { bid.final_ct_usd_price.checked_mul_int(bid.final_ct_amount).and_then( |ticket| acc.checked_add(&ticket) @@ -131,7 +131,7 @@ impl Pallet { } })?; - Ok((accepted_bid_len, 0)) + Ok((accepted_bid_len, rejected_bids.len() as u32)) } @@ -193,7 +193,6 @@ impl Pallet { // Determine how much funding has been achieved. let funding_amount_reached = project_details.funding_amount_reached_usd; - let fundraising_target = project_details.fundraising_target_usd; let fee_usd = Self::compute_total_fee_from_brackets(funding_amount_reached); let fee_percentage = Perquintill::from_rational(fee_usd, funding_amount_reached); diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 77fd70baf..998b8bb7a 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -288,7 +288,6 @@ impl< project_id: ProjectId, maybe_did: Option, expected_metadata: ProjectMetadataOf, - creation_start_block: BlockNumberFor, ) { let metadata = self.get_project_metadata(project_id); let details = self.get_project_details(project_id); diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 3e27070e6..e602dbbe1 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -718,10 +718,9 @@ pub mod pallet { IncorrectRound, /// Too early to execute the action. The action can likely be called again at a later stage. TooEarlyForRound, - /// A round transition was already executed, so the transition cannot be - /// executed again. This is likely to happen when the issuer manually transitions the project, - /// after which the automatic transition is executed. - RoundTransitionAlreadyHappened, + /// Too late to execute the action. Round has already ended, but transition to new + /// round has still to be executed. + TooLateForRound, /// A project's transition point (block number) was not set. TransitionPointNotSet, @@ -1190,24 +1189,6 @@ pub mod pallet { Self::do_mark_project_ct_migration_as_finished(project_id) } } - - fn update_weight(used_weight: &mut Weight, call: DispatchResultWithPostInfo, fallback_weight: Weight) { - match call { - Ok(post_dispatch_info) => - if let Some(actual_weight) = post_dispatch_info.actual_weight { - *used_weight = used_weight.saturating_add(actual_weight); - } else { - *used_weight = used_weight.saturating_add(fallback_weight); - }, - Err(DispatchErrorWithPostInfo:: { error: _error, post_info }) => { - if let Some(actual_weight) = post_info.actual_weight { - *used_weight = used_weight.saturating_add(actual_weight); - } else { - *used_weight = used_weight.saturating_add(fallback_weight); - } - }, - } - } } pub mod xcm_executor_impl { diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 07e4531d8..eb65bbd9f 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -27,7 +27,7 @@ use polimec_common::USD_DECIMALS; use polkadot_parachain_primitives::primitives::Id as ParaId; use serde::{Deserialize, Serialize}; use sp_arithmetic::{FixedPointNumber, FixedPointOperand, Percent}; -use sp_runtime::traits::{CheckedDiv, CheckedMul, One, Saturating, Zero}; +use sp_runtime::traits::{CheckedDiv, CheckedMul, One}; use sp_std::{cmp::Eq, prelude::*}; pub use storage_types::*; From 308d96552cebaa1a242409d9e82b303bd8ee6683 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios <54085674+JuaniRios@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:51:05 +0200 Subject: [PATCH 7/7] Update 6_settlement.rs --- pallets/funding/src/functions/6_settlement.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index af6168021..3c9952985 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -254,7 +254,7 @@ impl Pallet { Ok(()) } - /// Calculate the amount of funds the biider should receive back based on the original bid + /// Calculate the amount of funds the bidder should receive back based on the original bid /// amount and price compared to the final bid amount and price. fn calculate_refund(bid: &BidInfoOf) -> Result<(BalanceOf, BalanceOf), DispatchError> { let new_ticket_size =