Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Https /linear.app/polimec/issue/plmc 235/implement ct mint for contributions #59

276 changes: 148 additions & 128 deletions pallets/funding/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ use frame_support::{
ensure,
pallet_prelude::DispatchError,
traits::{
fungible::{InspectHold, MutateHold as FungibleMutateHold},
fungibles::{metadata::Mutate as MetadataMutate, Create, Mutate as FungiblesMutate},
tokens::{Precision, Preservation},
Get, Len,
fungible::MutateHold as FungibleMutateHold,
fungibles::{metadata::Mutate as MetadataMutate, Create, Inspect, Mutate as FungiblesMutate},
tokens::{Fortitude, Precision, Preservation, Restriction},
Get,
},
};

Expand Down Expand Up @@ -596,10 +596,10 @@ impl<T: Config> Pallet<T> {

// * Update Storage *
if funding_ratio <= Perquintill::from_percent(33u64) {
project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed(vec![]);
project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed;
Self::make_project_funding_fail(project_id, project_details, FailureReason::TargetNotReached, 1u32.into())
} else if funding_ratio <= Perquintill::from_percent(75u64) {
project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed(vec![]);
project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed;
project_details.status = ProjectStatus::AwaitingProjectDecision;
Self::add_to_update_store(
now + T::ManualAcceptanceDuration::get() + 1u32.into(),
Expand Down Expand Up @@ -774,7 +774,7 @@ impl<T: Config> Pallet<T> {
let mut project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectInfoNotFound)?;
let now = <frame_system::Pallet<T>>::block_number();
let evaluation_id = Self::next_evaluation_id();
let mut caller_existing_evaluations: Vec<(StorageItemIdOf<T>, EvaluationInfoOf<T>)> =
let caller_existing_evaluations: Vec<(StorageItemIdOf<T>, EvaluationInfoOf<T>)> =
Evaluations::<T>::iter_prefix((project_id, evaluator.clone())).collect();
let plmc_usd_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).ok_or(Error::<T>::PLMCPriceNotAvailable)?;
let early_evaluation_reward_threshold_usd =
Expand Down Expand Up @@ -887,7 +887,7 @@ impl<T: Config> Pallet<T> {
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectInfoNotFound)?;
let now = <frame_system::Pallet<T>>::block_number();
let bid_id = Self::next_bid_id();
let mut existing_bids = Bids::<T>::iter_prefix_values((project_id, bidder.clone())).collect::<Vec<_>>();
let existing_bids = Bids::<T>::iter_prefix_values((project_id, bidder.clone())).collect::<Vec<_>>();

let ticket_size = ct_usd_price.checked_mul_int(ct_amount).ok_or(Error::<T>::BadMath)?;
let funding_asset_usd_price =
Expand Down Expand Up @@ -935,6 +935,7 @@ impl<T: Config> Pallet<T> {
ct_vesting_period,
when: now,
funds_released: false,
ct_minted: false,
};

// * Update storage *
Expand Down Expand Up @@ -1000,7 +1001,7 @@ impl<T: Config> Pallet<T> {
let mut project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectInfoNotFound)?;
let now = <frame_system::Pallet<T>>::block_number();
let contribution_id = Self::next_contribution_id();
let mut existing_contributions =
let existing_contributions =
Contributions::<T>::iter_prefix_values((project_id, contributor.clone())).collect::<Vec<_>>();

let ct_usd_price = project_details.weighted_average_price.ok_or(Error::<T>::AuctionNotStarted)?;
Expand Down Expand Up @@ -1063,6 +1064,7 @@ impl<T: Config> Pallet<T> {
plmc_vesting_period,
ct_vesting_period,
funds_released: false,
ct_minted: false,
};

// * Update storage *
Expand Down Expand Up @@ -1105,8 +1107,10 @@ impl<T: Config> Pallet<T> {
Contributions::<T>::insert((project_id, contributor.clone(), contribution_id), new_contribution.clone());
NextContributionId::<T>::set(contribution_id.saturating_add(One::one()));

project_details.remaining_contribution_tokens = project_details.remaining_contribution_tokens.saturating_sub(new_contribution.ct_amount);
project_details.funding_amount_reached = project_details.funding_amount_reached.saturating_add(new_contribution.usd_contribution_amount);
project_details.remaining_contribution_tokens =
project_details.remaining_contribution_tokens.saturating_sub(new_contribution.ct_amount);
project_details.funding_amount_reached =
project_details.funding_amount_reached.saturating_add(new_contribution.usd_contribution_amount);
ProjectsDetails::<T>::insert(project_id, project_details);

// If no CTs remain, end the funding phase
Expand Down Expand Up @@ -1201,58 +1205,72 @@ impl<T: Config> Pallet<T> {
Ok(())
}

/// Mint contribution tokens after a step in the vesting period for a successful bid.
///
/// # Arguments
/// * bidder: The account who made bids
/// * project_id: The project the bids where made for
///
/// # Storage access
///
/// * `AuctionsInfo` - Check if its time to mint some tokens based on the bid vesting period, and update the bid after minting.
/// * `T::ContributionTokenCurrency` - Mint the tokens to the bidder
pub fn do_vested_contribution_token_bid_mint_for(
pub fn do_bid_ct_mint_for(
releaser: AccountIdOf<T>,
project_id: T::ProjectIdentifier,
bidder: AccountIdOf<T>,
) -> Result<(), DispatchError> {
bid_id: T::StorageItemId,
) -> DispatchResult {
// * Get variables *
let bids = Bids::<T>::iter_prefix_values((project_id, bidder.clone()));
let now = <frame_system::Pallet<T>>::block_number();
for mut bid in bids {
let mut ct_vesting = bid.ct_vesting_period;
let mut mint_amount: BalanceOf<T> = 0u32.into();
let mut bid = Bids::<T>::get((project_id, bidder.clone(), bid_id)).ok_or(Error::<T>::BidNotFound)?;
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectNotFound)?;
let ct_amount = bid.final_ct_amount;

// * Validity checks *
// check that it is not too early to withdraw the next amount
if ct_vesting.next_withdrawal > now {
continue
}
// * Validity checks *
ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::<T>::NotAllowed);
ensure!(bid.ct_minted == false, Error::<T>::NotAllowed);
ensure!(matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)), Error::<T>::NotAllowed);
ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::<T>::CannotClaimYet);

// * Calculate variables *
// Update vesting period until the next withdrawal is in the future
while let Ok(amount) = ct_vesting.calculate_next_withdrawal() {
mint_amount = mint_amount.saturating_add(amount);
if ct_vesting.next_withdrawal > now {
break
}
}
bid.ct_vesting_period = ct_vesting;
// * Calculate variables *
bid.ct_minted = true;

// * Update storage *
// TODO: Should we mint here, or should the full mint happen to the treasury and then do transfers from there?
// Mint the funds for the user
T::ContributionTokenCurrency::mint_into(bid.project_id, &bid.bidder, mint_amount)?;
Bids::<T>::insert((project_id, bidder.clone(), bid.id), bid.clone());
// * Update storage *
T::ContributionTokenCurrency::mint_into(project_id, &bid.bidder, ct_amount)?;
Bids::<T>::insert((project_id, bidder.clone(), bid_id), bid.clone());

// * Emit events *
Self::deposit_event(Event::<T>::ContributionTokenMinted {
caller: releaser.clone(),
project_id,
contributor: bidder.clone(),
amount: mint_amount,
})
}
// * Emit events *
Self::deposit_event(Event::<T>::ContributionTokenMinted {
releaser,
project_id: bid.project_id,
claimer: bidder,
amount: ct_amount,
});

Ok(())
}

pub fn do_contribution_ct_mint_for(
releaser: AccountIdOf<T>,
project_id: T::ProjectIdentifier,
contributor: AccountIdOf<T>,
contribution_id: T::StorageItemId,
) -> DispatchResult {
// * Get variables *
let mut contribution = Contributions::<T>::get((project_id, contributor.clone(), contribution_id))
.ok_or(Error::<T>::BidNotFound)?;
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectNotFound)?;
let ct_amount = contribution.ct_amount;

// * Validity checks *
ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::<T>::NotAllowed);
ensure!(contribution.ct_minted == false, Error::<T>::NotAllowed);
ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::<T>::CannotClaimYet);

// * Calculate variables *
contribution.ct_minted = true;

// * Update storage *
T::ContributionTokenCurrency::mint_into(project_id, &contribution.contributor, ct_amount)?;
Contributions::<T>::insert((project_id, contributor.clone(), contribution_id), contribution.clone());

// * Emit events *
Self::deposit_event(Event::<T>::ContributionTokenMinted {
releaser,
project_id: contribution.project_id,
claimer: contributor,
amount: ct_amount,
});

Ok(())
}
Expand Down Expand Up @@ -1328,65 +1346,6 @@ impl<T: Config> Pallet<T> {
Ok(())
}

/// Mint contribution tokens after a step in the vesting period for a contribution.
///
/// # Arguments
/// * claimer: The account who made the contribution
/// * project_id: The project the contribution was made for
///
/// # Storage access
/// * [`ProjectsDetails`] - Check that the funding period ended
/// * [`Contributions`] - Check if its time to mint some tokens based on the contributions vesting periods, and update the contribution after minting.
/// * [`T::ContributionTokenCurrency`] - Mint the tokens to the claimer
pub fn do_vested_contribution_token_purchase_mint_for(
releaser: AccountIdOf<T>,
project_id: T::ProjectIdentifier,
claimer: AccountIdOf<T>,
) -> Result<(), DispatchError> {
// * Get variables *
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectNotFound)?;
let contributions = Contributions::<T>::iter_prefix_values((project_id, &claimer));
let now = <frame_system::Pallet<T>>::block_number();

// * Validity checks *
ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::<T>::CannotClaimYet);
// TODO: PLMC-160. Check the flow of the final_price if the final price discovery during the Auction Round fails

for mut contribution in contributions {
let mut ct_vesting = contribution.ct_vesting_period;
let mut mint_amount: BalanceOf<T> = 0u32.into();

// * Validity checks *
// check that it is not too early to withdraw the next amount
if ct_vesting.next_withdrawal > now {
continue
}

// * Calculate variables *
// Update vesting period until the next withdrawal is in the future
while let Ok(amount) = ct_vesting.calculate_next_withdrawal() {
mint_amount = mint_amount.saturating_add(amount);
if ct_vesting.next_withdrawal > now {
break
}
}
contribution.ct_vesting_period = ct_vesting;

Contributions::<T>::insert((project_id, contribution.contributor.clone(), contribution.id), contribution.clone());

// * Emit events *
Self::deposit_event(Event::ContributionTokenMinted {
caller: releaser.clone(),
project_id,
contributor: claimer.clone(),
amount: mint_amount,
})
}

// * Update storage *
Ok(())
}

pub fn do_evaluation_unbond_for(
releaser: AccountIdOf<T>,
project_id: T::ProjectIdentifier,
Expand All @@ -1400,7 +1359,8 @@ impl<T: Config> Pallet<T> {

// * Validity checks *
ensure!(
released_evaluation.rewarded_or_slashed == true &&
(project_details.evaluation_round_info.evaluators_outcome == EvaluatorsOutcomeOf::<T>::Unchanged ||
released_evaluation.rewarded_or_slashed == true) &&
matches!(
project_details.status,
ProjectStatus::EvaluationFailed | ProjectStatus::FundingFailed | ProjectStatus::FundingSuccessful
Expand Down Expand Up @@ -1428,7 +1388,7 @@ impl<T: Config> Pallet<T> {
Ok(())
}

pub fn do_evaluation_reward(
pub fn do_evaluation_reward_payout_for(
caller: AccountIdOf<T>,
project_id: T::ProjectIdentifier,
evaluator: AccountIdOf<T>,
Expand Down Expand Up @@ -1485,6 +1445,60 @@ impl<T: Config> Pallet<T> {
Ok(())
}

pub fn do_evaluation_slash_for(
caller: AccountIdOf<T>,
project_id: T::ProjectIdentifier,
evaluator: AccountIdOf<T>,
evaluation_id: StorageItemIdOf<T>,
) -> Result<(), DispatchError> {
// * Get variables *
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectInfoNotFound)?;
let slash_percentage = T::EvaluatorSlash::get();
let treasury_account = T::TreasuryAccount::get();

let mut user_evaluations = Evaluations::<T>::iter_prefix_values((project_id, evaluator.clone()));
let mut evaluation =
user_evaluations.find(|evaluation| evaluation.id == evaluation_id).ok_or(Error::<T>::EvaluationNotFound)?;

// * Validity checks *
ensure!(
evaluation.rewarded_or_slashed == false &&
matches!(project_details.evaluation_round_info.evaluators_outcome, EvaluatorsOutcome::Slashed),
Error::<T>::NotAllowed
);

// * Calculate variables *
// We need to make sure that the current plmc bond is always >= than the slash amount.
let slashed_amount = slash_percentage * evaluation.original_plmc_bond;

// * Update storage *
evaluation.rewarded_or_slashed = true;

T::NativeCurrency::transfer_on_hold(
&LockType::Evaluation(project_id),
&evaluator,
&treasury_account,
slashed_amount,
Precision::Exact,
Restriction::Free,
Fortitude::Force,
)?;

evaluation.current_plmc_bond = evaluation.current_plmc_bond.saturating_sub(slashed_amount);
Evaluations::<T>::insert((project_id, evaluator.clone(), evaluation.id), evaluation);

// * Emit events *
Self::deposit_event(Event::<T>::EvaluationSlashed {
project_id,
evaluator: evaluator.clone(),
id: evaluation_id,
amount: slashed_amount,
caller,
});

Ok(())
}

pub fn do_release_bid_funds_for(
_caller: AccountIdOf<T>,
_project_id: T::ProjectIdentifier,
Expand Down Expand Up @@ -1938,20 +1952,26 @@ impl<T: Config> Pallet<T> {
amount: BalanceOf<T>,
) -> Result<(), DispatchError> {
// Check if the user has already locked tokens in the evaluation period
let evaluation_bonded = <T as Config>::NativeCurrency::balance_on_hold(&LockType::Evaluation(project_id), who);
let user_evaluations = Evaluations::<T>::iter_prefix_values((project_id, who.clone()));

let new_amount_to_lock = amount.saturating_sub(evaluation_bonded);
let evaluation_bonded_to_change_lock = amount.saturating_sub(new_amount_to_lock);

T::NativeCurrency::release(
&LockType::Evaluation(project_id),
who,
evaluation_bonded_to_change_lock,
Precision::Exact,
)
.map_err(|_| Error::<T>::ImpossibleState)?;
let mut to_convert = amount;
for mut evaluation in user_evaluations {
if to_convert == Zero::zero() {
break
}
let slash_deposit = <T as Config>::EvaluatorSlash::get() * evaluation.original_plmc_bond;
let available_to_convert = evaluation.current_plmc_bond.saturating_sub(slash_deposit);
let converted = to_convert.min(available_to_convert);
evaluation.current_plmc_bond = evaluation.current_plmc_bond.saturating_sub(converted);
Evaluations::<T>::insert((project_id, who.clone(), evaluation.id), evaluation);
T::NativeCurrency::release(&LockType::Evaluation(project_id), who, converted, Precision::Exact)
.map_err(|_| Error::<T>::ImpossibleState)?;
T::NativeCurrency::hold(&LockType::Participation(project_id), who, converted)
.map_err(|_| Error::<T>::ImpossibleState)?;
to_convert = to_convert.saturating_sub(converted)
}

T::NativeCurrency::hold(&LockType::Participation(project_id), who, amount)
T::NativeCurrency::hold(&LockType::Participation(project_id), who, to_convert)
.map_err(|_| Error::<T>::InsufficientBalance)?;

Ok(())
Expand Down
Loading
Loading