Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 17 additions & 26 deletions pallets/funding/src/functions/2_evaluation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,22 @@ impl<T: Config> Pallet<T> {
pub fn do_evaluate(
evaluator: &AccountIdOf<T>,
project_id: ProjectId,
usd_amount: Balance,
plmc_bond: Balance,
did: Did,
whitelisted_policy: Cid,
receiving_account: Junction,
) -> DispatchResult {
// * Get variables *
let project_metadata = ProjectsMetadata::<T>::get(project_id).ok_or(Error::<T>::ProjectMetadataNotFound)?;
let mut project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
let now = <T as Config>::BlockNumberProvider::current_block_number();
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
let now = BlockProviderFor::<T>::current_block_number();
let evaluation_id = NextEvaluationId::<T>::get();
let plmc_usd_price = <PriceProviderOf<T>>::get_decimals_aware_price(&Location::here(), PLMC_DECIMALS)
let plmc_usd_price = PriceProviderOf::<T>::get_decimals_aware_price(&Location::here(), PLMC_DECIMALS)
.ok_or(Error::<T>::PriceNotFound)?;
let early_evaluation_reward_threshold_usd =
T::EvaluationSuccessThreshold::get() * project_details.fundraising_target_usd;
let evaluation_round_info = &mut project_details.evaluation_round_info;
let project_policy = project_metadata.policy_ipfs_cid.ok_or(Error::<T>::ImpossibleState)?;
let project_metadata = ProjectsMetadata::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
let usd_amount = plmc_usd_price.checked_mul_int(plmc_bond).ok_or(Error::<T>::BadMath)?;

// * Validity Checks *
ensure!(project_policy == whitelisted_policy, Error::<T>::PolicyMismatch);
Expand All @@ -108,24 +107,11 @@ impl<T: Config> Pallet<T> {
Error::<T>::UnsupportedReceiverAccountJunction
);

let plmc_bond = plmc_usd_price
.reciprocal()
.ok_or(Error::<T>::BadMath)?
.checked_mul_int(usd_amount)
.ok_or(Error::<T>::BadMath)?;
let previous_total_evaluation_bonded_usd = evaluation_round_info.total_bonded_usd;

let remaining_bond_to_reach_threshold =
early_evaluation_reward_threshold_usd.saturating_sub(previous_total_evaluation_bonded_usd);

let early_usd_amount = if usd_amount <= remaining_bond_to_reach_threshold {
usd_amount
} else {
remaining_bond_to_reach_threshold
};

let previously_bonded_usd = project_details.evaluation_round_info.total_bonded_usd;
let remaining_for_early_reward_usd =
early_evaluation_reward_threshold_usd.saturating_sub(previously_bonded_usd);
let early_usd_amount = usd_amount.min(remaining_for_early_reward_usd);
let late_usd_amount = usd_amount.checked_sub(early_usd_amount).ok_or(Error::<T>::BadMath)?;

let new_evaluation = EvaluationInfoOf::<T> {
id: evaluation_id,
did: did.clone(),
Expand All @@ -142,9 +128,14 @@ impl<T: Config> Pallet<T> {
T::NativeCurrency::hold(&HoldReason::Evaluation.into(), evaluator, plmc_bond)?;
Evaluations::<T>::insert((project_id, evaluator, evaluation_id), new_evaluation);
NextEvaluationId::<T>::set(evaluation_id.saturating_add(One::one()));
evaluation_round_info.total_bonded_usd = evaluation_round_info.total_bonded_usd.saturating_add(usd_amount);
evaluation_round_info.total_bonded_plmc = evaluation_round_info.total_bonded_plmc.saturating_add(plmc_bond);
ProjectsDetails::<T>::insert(project_id, project_details);
ProjectsDetails::<T>::mutate(project_id, |details| {
if let Some(details) = details {
details.evaluation_round_info.total_bonded_usd =
details.evaluation_round_info.total_bonded_usd.saturating_add(usd_amount);
details.evaluation_round_info.total_bonded_plmc =
details.evaluation_round_info.total_bonded_plmc.saturating_add(plmc_bond);
}
});

// * Emit events *
Self::deposit_event(Event::Evaluation {
Expand Down
82 changes: 44 additions & 38 deletions pallets/funding/src/instantiator/calculations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,7 @@ impl<
&mut self,
evaluations: Vec<EvaluationParams<T>>,
) -> Vec<UserToPLMCBalance<T>> {
let plmc_usd_price =
self.execute(|| <PriceProviderOf<T>>::get_decimals_aware_price(&Location::here(), PLMC_DECIMALS).unwrap());

let mut output = Vec::new();
for eval in evaluations {
let usd_bond = eval.usd_amount;
let plmc_bond = plmc_usd_price.reciprocal().unwrap().saturating_mul_int(usd_bond);

output.push(UserToPLMCBalance::new(eval.account, plmc_bond));
}
output
evaluations.into_iter().map(|eval| UserToPLMCBalance::new(eval.account, eval.plmc_amount)).collect()
}

// A single bid can be split into multiple buckets. This function splits the bid into multiple ones at different prices.
Expand Down Expand Up @@ -353,45 +343,54 @@ impl<
output
}

pub fn generate_evaluations_from_total_usd(
pub fn generate_evaluations_from_total_plmc(
&self,
usd_amount: Balance,
total_plmc_amount: Balance, // This is the total PLMC to be distributed
evaluations_count: u8,
) -> Vec<EvaluationParams<T>> {
// Even distribution of weights totaling 100% among bids.
let weights = {
if evaluations_count == 0 {
return vec![];
}
let base = 100 / evaluations_count;
let remainder = 100 % evaluations_count;
let mut result = vec![base; evaluations_count as usize];
for i in 0..remainder {
result[i as usize] += 1;
}
result
};
if evaluations_count == 0 {
return vec![];
}

let evaluators = (0..evaluations_count as u32).map(|i| self.account_from_u32(i, "EVALUATOR")).collect_vec();
zip(evaluators, weights)
.map(|(evaluator, weight)| {
let ticket_size = Percent::from_percent(weight) * usd_amount;
(evaluator, ticket_size).into()
})
.collect()
let mut evaluations = Vec::with_capacity(evaluations_count as usize);
let base_weight = 100 / evaluations_count;
let remainder = 100 % evaluations_count;

for i in 0..evaluations_count {
let evaluator_account = self.account_from_u32(i as u32, "EVALUATOR");
let weight_for_evaluator = base_weight + if i < remainder { 1 } else { 0 };

// Calculate PLMC amount for this evaluator based on weight
let plmc_for_evaluator = Percent::from_percent(weight_for_evaluator) * total_plmc_amount;

evaluations.push(EvaluationParams::from((evaluator_account, plmc_for_evaluator)));
}

evaluations
}

pub fn generate_successful_evaluations(
&self,
project_metadata: ProjectMetadataOf<T>,
evaluations_count: u8,
) -> Vec<EvaluationParams<T>> {
let funding_target = project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size);
let funding_target_usd =
project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size);
// if we use just the threshold, then for big usd targets we lose the evaluation due to PLMC conversion errors in `evaluation_end`
let evaluation_success_threshold = 100;
let usd_threshold = Percent::from_percent(evaluation_success_threshold) * funding_target;
let target_usd_for_success = Percent::from_percent(100) * funding_target_usd;

self.generate_evaluations_from_total_usd(usd_threshold, evaluations_count)
let plmc_usd_price = <PriceProviderOf<T>>::get_decimals_aware_price(&Location::here(), PLMC_DECIMALS).unwrap();
// We want to find PLMC amount such that: PLMC_amount * price_of_plmc_in_usd = target_usd_for_success
// So, PLMC_amount = target_usd_for_success / price_of_plmc_in_usd
// Which is target_usd_for_success * (1 / price_of_plmc_in_usd)
let price_reciprocal =
plmc_usd_price.reciprocal().expect("Price reciprocal failed in test; price cannot be zero");

let total_plmc_for_success = price_reciprocal
.checked_mul_int(target_usd_for_success)
.expect("Failed to calculate total PLMC for success in test (multiplication error)");

self.generate_evaluations_from_total_plmc(total_plmc_for_success, evaluations_count)
}

pub fn generate_failing_evaluations(
Expand All @@ -404,8 +403,15 @@ impl<
let evaluation_fail_percent = <T as Config>::EvaluationSuccessThreshold::get().deconstruct() / 2;

let usd_threshold = Percent::from_percent(evaluation_fail_percent) * funding_target;
let plmc_usd_price = <PriceProviderOf<T>>::get_decimals_aware_price(&Location::here(), PLMC_DECIMALS).unwrap();
let price_reciprocal =
plmc_usd_price.reciprocal().expect("Price reciprocal failed in test; price cannot be zero");

let total_plmc_for_failure = price_reciprocal
.checked_mul_int(usd_threshold)
.expect("Failed to calculate total PLMC for failure in test (multiplication error)");

self.generate_evaluations_from_total_usd(usd_threshold, evaluations_count)
self.generate_evaluations_from_total_plmc(total_plmc_for_failure, evaluations_count)
}

pub fn generate_bids_from_total_ct_amount(&self, bids_count: u32, total_ct_bid: Balance) -> Vec<BidParams<T>> {
Expand Down
4 changes: 2 additions & 2 deletions pallets/funding/src/instantiator/chain_interactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,12 @@ impl<

pub fn evaluate_for_users(&mut self, project_id: ProjectId, bonds: Vec<EvaluationParams<T>>) -> DispatchResult {
let project_policy = self.get_project_metadata(project_id).policy_ipfs_cid.unwrap();
for EvaluationParams { account, usd_amount, receiving_account } in bonds {
for EvaluationParams { account, plmc_amount, receiving_account } in bonds {
self.execute(|| {
crate::Pallet::<T>::do_evaluate(
&account.clone(),
project_id,
usd_amount,
plmc_amount,
generate_did_from_account(account.clone()),
project_policy.clone(),
receiving_account,
Expand Down
2 changes: 1 addition & 1 deletion pallets/funding/src/instantiator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extern crate alloc;

use crate::{traits::*, *};
use alloc::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
use core::{cell::RefCell, iter::zip, marker::PhantomData};
use core::{cell::RefCell, marker::PhantomData};
use frame_support::{
pallet_prelude::*,
traits::{
Expand Down
20 changes: 8 additions & 12 deletions pallets/funding/src/instantiator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,37 +97,33 @@ impl<T: Config> Total for Vec<UserToPLMCBalance<T>> {
#[serde(rename_all = "camelCase", deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))]
pub struct EvaluationParams<T: Config> {
pub account: AccountIdOf<T>,
pub usd_amount: Balance,
pub plmc_amount: Balance,
pub receiving_account: Junction,
}
impl<T: Config> EvaluationParams<T> {
pub const fn new(account: AccountIdOf<T>, usd_amount: Balance, receiving_account: Junction) -> Self {
EvaluationParams::<T> { account, usd_amount, receiving_account }
pub const fn new(account: AccountIdOf<T>, plmc_amount: Balance, receiving_account: Junction) -> Self {
EvaluationParams::<T> { account, plmc_amount, receiving_account }
}
}
impl<T: Config> From<(AccountIdOf<T>, Balance, Junction)> for EvaluationParams<T> {
fn from((account, usd_amount, receiving_account): (AccountIdOf<T>, Balance, Junction)) -> Self {
EvaluationParams::<T>::new(account, usd_amount, receiving_account)
fn from((account, plmc_amount, receiving_account): (AccountIdOf<T>, Balance, Junction)) -> Self {
EvaluationParams::<T>::new(account, plmc_amount, receiving_account)
}
}
impl<T: Config> From<(AccountIdOf<T>, Balance)> for EvaluationParams<T> {
fn from((account, usd_amount): (AccountIdOf<T>, Balance)) -> Self {
fn from((account, plmc_amount): (AccountIdOf<T>, Balance)) -> Self {
let receiving_account = Junction::AccountId32 {
network: Some(NetworkId::Polkadot),
id: T::AccountId32Conversion::convert(account.clone()),
};
EvaluationParams::<T>::new(account, usd_amount, receiving_account)
EvaluationParams::<T>::new(account, plmc_amount, receiving_account)
}
}
impl<T: Config> Accounts for Vec<EvaluationParams<T>> {
type Account = AccountIdOf<T>;

fn accounts(&self) -> Vec<Self::Account> {
let mut btree = BTreeSet::new();
for EvaluationParams { account, usd_amount: _, receiving_account: _ } in self {
btree.insert(account.clone());
}
btree.into_iter().collect_vec()
self.iter().map(|params| params.account.clone()).collect::<BTreeSet<_>>().into_iter().collect_vec()
}
}

Expand Down
9 changes: 5 additions & 4 deletions pallets/funding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ pub type VestingOf<T> = pallet_linear_release::Pallet<T>;
pub type BlockNumberToBalanceOf<T> = <T as pallet_linear_release::Config>::BlockNumberToBalance;
pub type RuntimeHoldReasonOf<T> = <T as Config>::RuntimeHoldReason;
pub type PriceProviderOf<T> = <T as Config>::PriceProvider;
pub type BlockProviderFor<T> = <T as Config>::BlockNumberProvider;
pub type BlockNumberFor<T> = <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;

#[frame_support::pallet]
Expand Down Expand Up @@ -683,7 +684,7 @@ pub mod pallet {
origin: OriginFor<T>,
jwt: UntrustedToken,
project_id: ProjectId,
#[pallet::compact] usd_amount: Balance,
#[pallet::compact] plmc_bond: Balance,
) -> DispatchResult {
let (account, did, _investor_type, whitelisted_policy) =
T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?;
Expand All @@ -693,7 +694,7 @@ pub mod pallet {
id: T::AccountId32Conversion::convert(account.clone()),
};

Self::do_evaluate(&account, project_id, usd_amount, did, whitelisted_policy, receiving_account)
Self::do_evaluate(&account, project_id, plmc_bond, did, whitelisted_policy, receiving_account)
}

#[pallet::call_index(5)]
Expand All @@ -702,7 +703,7 @@ pub mod pallet {
origin: OriginFor<T>,
jwt: UntrustedToken,
project_id: ProjectId,
#[pallet::compact] usd_amount: Balance,
#[pallet::compact] plmc_bond: Balance,
receiving_account: Junction,
signature_bytes: [u8; 65],
) -> DispatchResult {
Expand All @@ -711,7 +712,7 @@ pub mod pallet {

Self::verify_receiving_account_signature(&account, project_id, &receiving_account, signature_bytes)?;

Self::do_evaluate(&account, project_id, usd_amount, did, whitelisted_policy, receiving_account)
Self::do_evaluate(&account, project_id, plmc_bond, did, whitelisted_policy, receiving_account)
}

#[pallet::call_index(6)]
Expand Down
2 changes: 0 additions & 2 deletions pallets/funding/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,6 @@ impl pallet_timestamp::Config for TestRuntime {
type WeightInfo = ();
}

pub const HOURS: BlockNumber = 300u64;

// REMARK: In the production configuration we use DAYS instead of HOURS.
// We need all durations to use different times to catch bugs in the tests.
parameter_types! {
Expand Down
Loading