Skip to content

Commit

Permalink
Merge pull request #45 from Polimec/feature/plmc-209-bondtype-changes
Browse files Browse the repository at this point in the history
Bondtype changes
  • Loading branch information
lrazovic committed Jun 26, 2023
2 parents c2214e0 + f5de088 commit 28c96e9
Show file tree
Hide file tree
Showing 5 changed files with 1,102 additions and 253 deletions.
208 changes: 117 additions & 91 deletions polimec-skeleton/pallets/funding/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use super::*;

use crate::traits::{BondingRequirementCalculation, ProvideStatemintPrice};
use frame_support::traits::fungible::InspectHold;
use frame_support::traits::tokens::{Precision, Preservation};
use frame_support::{
ensure,
Expand Down Expand Up @@ -721,7 +722,7 @@ impl<T: Config> Pallet<T> {
Ok(())
}
// Note: usd_amount needs to have the same amount of decimals as PLMC,, so when multiplied by the plmc-usd price, it gives us the PLMC amount with the decimals we wanted.
pub fn do_evaluation(
pub fn do_evaluate(
evaluator: AccountIdOf<T>, project_id: T::ProjectIdentifier, usd_amount: BalanceOf<T>,
) -> Result<(), DispatchError> {
// * Get variables *
Expand Down Expand Up @@ -763,28 +764,27 @@ impl<T: Config> Pallet<T> {

match existing_evaluations.try_push(new_evaluation.clone()) {
Ok(_) => {
T::NativeCurrency::hold(&BondType::Evaluation(project_id), &evaluator, plmc_bond)
T::NativeCurrency::hold(&LockType::Evaluation(project_id), &evaluator, plmc_bond)
.map_err(|_| Error::<T>::InsufficientBalance)?;
}
Err(_) => {
// Evaluations are stored in descending order. If the evaluation vector for the user is full, we drop the lowest/last bond
let lowest_evaluation_index: usize = (T::MaxEvaluationsPerUser::get() - 1)
.try_into()
.map_err(|_| Error::<T>::BadMath)?;
let lowest_evaluation = existing_evaluations.swap_remove(lowest_evaluation_index);
let lowest_evaluation = existing_evaluations.swap_remove(existing_evaluations.len() - 1);

ensure!(
lowest_evaluation.plmc_bond < plmc_bond,
Error::<T>::EvaluationBondTooLow
);

T::NativeCurrency::release(
&BondType::Evaluation(project_id),
&LockType::Evaluation(project_id),
&lowest_evaluation.evaluator,
lowest_evaluation.plmc_bond,
Precision::Exact,
)
.map_err(|_| Error::<T>::InsufficientBalance)?;

T::NativeCurrency::hold(&BondType::Evaluation(project_id), &evaluator, plmc_bond)
T::NativeCurrency::hold(&LockType::Evaluation(project_id), &evaluator, plmc_bond)
.map_err(|_| Error::<T>::InsufficientBalance)?;

// This should never fail since we just removed an element from the vector
Expand Down Expand Up @@ -832,7 +832,7 @@ impl<T: Config> Pallet<T> {

// * Update Storage *
T::NativeCurrency::release(
&BondType::Evaluation(project_id),
&LockType::Evaluation(project_id),
&evaluation.evaluator.clone(),
evaluation.plmc_bond,
Precision::Exact,
Expand Down Expand Up @@ -904,7 +904,6 @@ impl<T: Config> Pallet<T> {
);

// * Calculate new variables *
let holding_account = Self::fund_account_id(project_id);
let (plmc_vesting_period, ct_vesting_period) =
Self::calculate_vesting_periods(bidder.clone(), multiplier, ct_amount, ct_usd_price)
.map_err(|_| Error::<T>::BadMath)?;
Expand Down Expand Up @@ -934,52 +933,37 @@ impl<T: Config> Pallet<T> {
// * Update storage *
match existing_bids.try_push(new_bid.clone()) {
Ok(_) => {
T::NativeCurrency::hold(&BondType::Bid(project_id), &bidder, required_plmc_bond)
.map_err(|_| Error::<T>::InsufficientBalance)?;
Self::try_plmc_participation_lock(&bidder, project_id, required_plmc_bond)?;
Self::try_funding_asset_hold(&bidder, project_id, required_funding_asset_transfer, asset_id)?;

T::FundingCurrency::transfer(
asset_id.into(),
&bidder,
&holding_account,
required_funding_asset_transfer,
Preservation::Expendable,
)?;
// TODO: PLMC-159. Send an XCM message to Statemint/e to transfer a `bid.market_cap` amount of USDC (or the Currency specified by the issuer) to the PalletId Account
// Alternative TODO: PLMC-159. The user should have the specified currency (e.g: USDC) already on Polimec
}
Err(_) => {
// Since the bids are sorted by price, and in this branch the Vec is full, the last element is the lowest bid
let lowest_bid_index: usize = (T::MaxBidsPerUser::get() - 1)
.try_into()
.map_err(|_| Error::<T>::BadMath)?;
let lowest_bid = existing_bids.swap_remove(lowest_bid_index);
let lowest_plmc_bond = existing_bids
.iter()
.last()
.ok_or(Error::<T>::ImpossibleState)?
.plmc_bond;

ensure!(new_bid.clone() > lowest_bid, Error::<T>::BidTooLow);
ensure!(
new_bid.plmc_bond > lowest_plmc_bond,
Error::<T>::BidTooLow
);

T::NativeCurrency::release(
&BondType::Bid(project_id),
&lowest_bid.bidder.clone(),
lowest_bid.plmc_bond,
Precision::Exact,
Self::release_last_funding_item_in_vec(
&bidder,
project_id,
asset_id,
&mut existing_bids,
|x| x.plmc_bond,
|x| x.funding_asset_amount,
)?;

T::NativeCurrency::hold(&BondType::Bid(project_id), &bidder, required_plmc_bond)
.map_err(|_| Error::<T>::InsufficientBalance)?;
Self::try_plmc_participation_lock(&bidder, project_id, required_plmc_bond)?;

T::FundingCurrency::transfer(
lowest_bid.funding_asset.to_statemint_id(),
&holding_account,
&lowest_bid.bidder.clone(),
lowest_bid.funding_asset_amount,
Preservation::Expendable,
)?;
T::FundingCurrency::transfer(
asset_id.into(),
&bidder,
&holding_account,
required_funding_asset_transfer,
Preservation::Expendable,
)?;
Self::try_funding_asset_hold(&bidder, project_id, required_funding_asset_transfer, asset_id)?;

// This should never fail, since we just removed an element from the Vec
existing_bids
Expand Down Expand Up @@ -1047,6 +1031,7 @@ impl<T: Config> Pallet<T> {
|| project_details.status == ProjectStatus::RemainderRound,
Error::<T>::AuctionNotStarted
);

if let Some(minimum_ticket_size) = project_metadata.ticket_size.minimum {
// Make sure the bid amount is greater than the minimum specified by the issuer
ensure!(ticket_size >= minimum_ticket_size, Error::<T>::ContributionTooLow);
Expand All @@ -1067,7 +1052,6 @@ impl<T: Config> Pallet<T> {
// );

// * Calculate variables *
let fund_account = Self::fund_account_id(project_id);
let buyable_tokens = if project_details.remaining_contribution_tokens > token_amount {
token_amount
} else {
Expand All @@ -1086,7 +1070,7 @@ impl<T: Config> Pallet<T> {
.ok_or(Error::<T>::BadMath)?
.saturating_mul_int(ticket_size);
let asset_id = asset.to_statemint_id();
let mut remaining_cts_after_purchase = project_details
let remaining_cts_after_purchase = project_details
.remaining_contribution_tokens
.saturating_sub(buyable_tokens);

Expand All @@ -1107,59 +1091,34 @@ impl<T: Config> Pallet<T> {
// Try adding the new contribution to the system
match existing_contributions.try_push(new_contribution.clone()) {
Ok(_) => {
T::NativeCurrency::hold(&BondType::Contribution(project_id), &contributor, required_plmc_bond)
.map_err(|_| Error::<T>::InsufficientBalance)?;

T::FundingCurrency::transfer(
asset_id.into(),
&contributor,
&fund_account,
required_funding_asset_transfer,
Preservation::Expendable,
)?;
Self::try_plmc_participation_lock(&contributor, project_id, required_plmc_bond)?;
Self::try_funding_asset_hold(&contributor, project_id, required_funding_asset_transfer, asset_id)?;
}
Err(_) => {
// The contributions are sorted by highest PLMC bond. If the contribution vector for the user is full, we drop the lowest/last item
let lowest_contribution_index: usize = (T::MaxContributionsPerUser::get() - 1)
.try_into()
.map_err(|_| Error::<T>::BadMath)?;
let lowest_contribution = existing_contributions.swap_remove(lowest_contribution_index);
remaining_cts_after_purchase =
remaining_cts_after_purchase.saturating_add(lowest_contribution.ct_amount);
let lowest_plmc_bond = existing_contributions
.iter()
.last()
.ok_or(Error::<T>::ImpossibleState)?
.plmc_bond;

ensure!(
new_contribution.plmc_bond > lowest_contribution.plmc_bond,
new_contribution.plmc_bond > lowest_plmc_bond,
Error::<T>::ContributionTooLow
);

// _528_589_814
// 1_585_769_442
//
T::NativeCurrency::release(
&BondType::Contribution(project_id),
&lowest_contribution.contributor,
lowest_contribution.plmc_bond,
Precision::Exact,
)?;

T::NativeCurrency::hold(&BondType::Contribution(project_id), &contributor, required_plmc_bond)
.map_err(|_| Error::<T>::InsufficientBalance)?;

T::FundingCurrency::transfer(
asset_id.into(),
&fund_account,
Self::release_last_funding_item_in_vec(
&contributor,
lowest_contribution.funding_asset_amount,
Preservation::Expendable,
project_id,
asset_id,
&mut existing_contributions,
|x| x.plmc_bond,
|x| x.funding_asset_amount,
)?;

T::FundingCurrency::transfer(
asset_id.into(),
&contributor,
&fund_account,
required_funding_asset_transfer,
Preservation::Expendable,
)?;
Self::try_plmc_participation_lock(&contributor, project_id, required_plmc_bond)?;

Self::try_funding_asset_hold(&contributor, project_id, required_funding_asset_transfer, asset_id)?;

// This should never fail, since we just removed an item from the vector
existing_contributions
Expand Down Expand Up @@ -1234,7 +1193,12 @@ impl<T: Config> Pallet<T> {

// * Update storage *
// TODO: check that the full amount was unreserved
T::NativeCurrency::release(&BondType::Bid(project_id), &bid.bidder, unbond_amount, Precision::Exact)?;
T::NativeCurrency::release(
&LockType::Participation(project_id),
&bid.bidder,
unbond_amount,
Precision::Exact,
)?;
new_bids.push(bid.clone());

// * Emit events *
Expand Down Expand Up @@ -1369,7 +1333,7 @@ impl<T: Config> Pallet<T> {
// TODO: Should we mint here, or should the full mint happen to the treasury and then do transfers from there?
// Unreserve the funds for the user
T::NativeCurrency::release(
&BondType::Contribution(project_id),
&LockType::Participation(project_id),
&claimer,
unbond_amount,
Precision::Exact,
Expand Down Expand Up @@ -1714,4 +1678,66 @@ impl<T: Config> Pallet<T> {
let zeroes: BalanceOf<T> = BalanceOf::<T>::from(10u64).saturating_pow(decimals.into());
number.saturating_mul(zeroes)
}

pub fn try_plmc_participation_lock(
who: &T::AccountId, project_id: T::ProjectIdentifier, 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 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)?;

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

Ok(())
}

// TODO(216): use the hold interface of the fungibles::MutateHold once its implemented on pallet_assets.
pub fn try_funding_asset_hold(
who: &T::AccountId, project_id: T::ProjectIdentifier, amount: BalanceOf<T>, asset_id: AssetIdOf<T>,
) -> Result<(), DispatchError> {
let fund_account = Self::fund_account_id(project_id);

T::FundingCurrency::transfer(asset_id, &who, &fund_account, amount, Preservation::Expendable)?;

Ok(())
}

// TODO(216): use the hold interface of the fungibles::MutateHold once its implemented on pallet_assets.
pub fn release_last_funding_item_in_vec<I, M>(
who: &T::AccountId, project_id: T::ProjectIdentifier, asset_id: AssetIdOf<T>, vec: &mut BoundedVec<I, M>,
plmc_getter: impl Fn(&I) -> BalanceOf<T>, funding_asset_getter: impl Fn(&I) -> BalanceOf<T>,
) -> Result<(), DispatchError> {
let fund_account = Self::fund_account_id(project_id);
let last_item = vec.swap_remove(vec.len() - 1);
let plmc_amount = plmc_getter(&last_item);
let funding_asset_amount = funding_asset_getter(&last_item);

T::NativeCurrency::release(
&LockType::Participation(project_id),
&who,
plmc_amount,
Precision::Exact,
)?;

T::FundingCurrency::transfer(
asset_id,
&fund_account,
&who,
funding_asset_amount,
Preservation::Expendable,
)?;

Ok(())
}
}
4 changes: 2 additions & 2 deletions polimec-skeleton/pallets/funding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ pub type BidInfoOf<T> = BidInfo<
>;
pub type ContributionInfoOf<T> =
ContributionInfo<StorageItemIdOf<T>, ProjectIdOf<T>, AccountIdOf<T>, BalanceOf<T>, VestingOf<T>, VestingOf<T>>;
pub type BondTypeOf<T> = BondType<ProjectIdOf<T>>;
pub type BondTypeOf<T> = LockType<ProjectIdOf<T>>;

const PLMC_STATEMINT_ID: u32 = 2069;

Expand Down Expand Up @@ -717,7 +717,7 @@ pub mod pallet {
origin: OriginFor<T>, project_id: T::ProjectIdentifier, #[pallet::compact] amount: BalanceOf<T>,
) -> DispatchResult {
let evaluator = ensure_signed(origin)?;
Self::do_evaluation(evaluator, project_id, amount)
Self::do_evaluate(evaluator, project_id, amount)
}

/// Release the bonded PLMC for an evaluator if the project assigned to it is in the EvaluationFailed phase
Expand Down
4 changes: 2 additions & 2 deletions polimec-skeleton/pallets/funding/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ impl pallet_balances::Config for TestRuntime {
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type ReserveIdentifier = BondType<u32>;
type HoldIdentifier = BondType<u32>;
type ReserveIdentifier = LockType<u32>;
type HoldIdentifier = LockType<u32>;
type FreezeIdentifier = ();
type MaxLocks = frame_support::traits::ConstU32<1024>;
type MaxReserves = frame_support::traits::ConstU32<1024>;
Expand Down
Loading

0 comments on commit 28c96e9

Please sign in to comment.