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

Feat/plmc 458 community round gatekeep based on did winning bid not account #196

Merged
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
35 changes: 28 additions & 7 deletions pallets/funding/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use sp_arithmetic::{
Percent, Perquintill,
};
use sp_runtime::traits::{Convert, ConvertBack};
use sp_std::marker::PhantomData;
use sp_std::{marker::PhantomData, ops::Not};
use xcm::v3::MaxDispatchErrorLen;

use super::*;
Expand Down Expand Up @@ -1166,6 +1166,7 @@ impl<T: Config> Pallet<T> {
id: bid_id,
project_id,
bidder: bidder.clone(),
did: did.clone(),
status: BidStatus::YetUnknown,
original_ct_amount: ct_amount,
original_ct_usd_price: ct_usd_price,
Expand Down Expand Up @@ -1214,13 +1215,10 @@ impl<T: Config> Pallet<T> {
investor_type: InvestorType,
) -> DispatchResultWithPostInfo {
let mut project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
ensure!(project_details.status == ProjectStatus::CommunityRound, Error::<T>::AuctionNotStarted);

let user_winning_bids = Bids::<T>::iter_prefix_values((project_id, contributor))
.filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)))
.next();
let did_has_winning_bid = DidWithWinningBids::<T>::get(project_id, did.clone());

ensure!(user_winning_bids.is_none(), Error::<T>::UserHasWinningBids);
ensure!(project_details.status == ProjectStatus::CommunityRound, Error::<T>::AuctionNotStarted);
ensure!(did_has_winning_bid.not(), Error::<T>::UserHasWinningBids);

let buyable_tokens = token_amount.min(project_details.remaining_contribution_tokens);
project_details.remaining_contribution_tokens.saturating_reduce(buyable_tokens);
Expand Down Expand Up @@ -1321,6 +1319,23 @@ impl<T: Config> Pallet<T> {
contributor_ticket_size.usd_ticket_below_maximum_per_did(total_usd_bought_by_did + ticket_size),
Error::<T>::ContributionTooHigh
);
ensure!(
project_metadata.participation_currencies.contains(&funding_asset),
Error::<T>::FundingAssetNotAccepted
);
ensure!(did.clone() != project_details.issuer_did, Error::<T>::ParticipationToThemselves);
ensure!(
caller_existing_contributions.len() < T::MaxContributionsPerUser::get() as usize,
Error::<T>::TooManyContributionsForUser
);
ensure!(
contributor_ticket_size.usd_ticket_above_minimum_per_participation(ticket_size),
Error::<T>::ContributionTooLow
);
ensure!(
contributor_ticket_size.usd_ticket_below_maximum_per_did(total_usd_bought_by_did + ticket_size),
Error::<T>::ContributionTooHigh
);

let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price)?;
let funding_asset_amount =
Expand Down Expand Up @@ -2614,12 +2629,18 @@ impl<T: Config> Pallet<T> {
bid_token_amount_sum.saturating_accrue(bid.original_ct_amount);
bid_usd_value_sum.saturating_accrue(ticket_size);
bid.status = BidStatus::Accepted;
DidWithWinningBids::<T>::mutate(project_id, bid.did.clone(), |flag| {
*flag = true;
});
} else {
accepted_bids_count += 1;
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);
DidWithWinningBids::<T>::mutate(project_id, bid.did.clone(), |flag| {
*flag = true;
});
bid.final_ct_amount = buyable_amount;

let funding_asset_price = T::PriceProvider::get_price(bid.funding_asset.to_assethub_id())
Expand Down
16 changes: 14 additions & 2 deletions pallets/funding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,16 @@ pub type ProjectDetailsOf<T> =
pub type EvaluationRoundInfoOf<T> = EvaluationRoundInfo<BalanceOf<T>>;
pub type VestingInfoOf<T> = VestingInfo<BlockNumberFor<T>, BalanceOf<T>>;
pub type EvaluationInfoOf<T> = EvaluationInfo<u32, ProjectId, AccountIdOf<T>, BalanceOf<T>, BlockNumberFor<T>>;
pub type BidInfoOf<T> =
BidInfo<ProjectId, BalanceOf<T>, PriceOf<T>, AccountIdOf<T>, BlockNumberFor<T>, MultiplierOf<T>, VestingInfoOf<T>>;
pub type BidInfoOf<T> = BidInfo<
ProjectId,
Did,
BalanceOf<T>,
PriceOf<T>,
AccountIdOf<T>,
BlockNumberFor<T>,
MultiplierOf<T>,
VestingInfoOf<T>,
>;
pub type ContributionInfoOf<T> =
ContributionInfo<u32, ProjectId, AccountIdOf<T>, BalanceOf<T>, MultiplierOf<T>, VestingInfoOf<T>>;

Expand Down Expand Up @@ -548,6 +556,10 @@ pub mod pallet {
ValueQuery,
>;

#[pallet::storage]
pub type DidWithWinningBids<T: Config> =
StorageDoubleMap<_, Blake2_128Concat, ProjectId, Blake2_128Concat, Did, bool, ValueQuery>;

#[pallet::storage]
/// Migrations sent and awaiting for confirmation
pub type UnconfirmedMigrations<T: Config> = StorageMap<_, Blake2_128Concat, QueryId, ProjectMigrationOriginsOf<T>>;
Expand Down
62 changes: 62 additions & 0 deletions pallets/funding/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2871,6 +2871,68 @@ mod community_contribution {
Error::<TestRuntime>::ParticipationToThemselves
);
}

#[test]
fn did_with_winning_bid_cannot_contribute() {
let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext())));
let project_metadata = default_project_metadata(0, ISSUER);
let bids = vec![
BidParams::new(BIDDER_1, 400_000 * ASSET_UNIT, 1u8, AcceptedFundingAsset::USDT),
BidParams::new(BIDDER_2, 50_000 * ASSET_UNIT, 1u8, AcceptedFundingAsset::USDT),
];

let project_id =
inst.create_community_contributing_project(project_metadata.clone(), ISSUER, default_evaluations(), bids);

let bidder_2_jwt = get_mock_jwt(BIDDER_2, InvestorType::Retail, generate_did_from_account(BIDDER_2));
let bidder_3_jwt_same_did = get_mock_jwt(BIDDER_3, InvestorType::Retail, generate_did_from_account(BIDDER_2));
let bidder_3_jwt_different_did =
get_mock_jwt(BIDDER_3, InvestorType::Retail, generate_did_from_account(BIDDER_3));

let plmc_mints = vec![(BIDDER_2, 420 * PLMC).into(), (BIDDER_3, 420 * PLMC).into()];
inst.mint_plmc_to(plmc_mints);
let usdt_mints = vec![(BIDDER_2, 420 * ASSET_UNIT).into(), (BIDDER_3, 420 * ASSET_UNIT).into()];
inst.mint_foreign_asset_to(usdt_mints);

inst.execute(|| {
assert_noop!(
Pallet::<TestRuntime>::community_contribute(
RuntimeOrigin::signed(BIDDER_2),
bidder_2_jwt,
project_id,
10 * ASSET_UNIT,
1u8.try_into().unwrap(),
AcceptedFundingAsset::USDT,
),
Error::<TestRuntime>::UserHasWinningBids
);
});

inst.execute(|| {
assert_noop!(
Pallet::<TestRuntime>::community_contribute(
RuntimeOrigin::signed(BIDDER_3),
bidder_3_jwt_same_did,
project_id,
10 * ASSET_UNIT,
1u8.try_into().unwrap(),
AcceptedFundingAsset::USDT,
),
Error::<TestRuntime>::UserHasWinningBids
);
});

inst.execute(|| {
assert_ok!(Pallet::<TestRuntime>::community_contribute(
RuntimeOrigin::signed(BIDDER_3),
bidder_3_jwt_different_did,
project_id,
10 * ASSET_UNIT,
1u8.try_into().unwrap(),
AcceptedFundingAsset::USDT,
));
});
}
}

// only functionalities that happen in the REMAINDER FUNDING period of a project
Expand Down
12 changes: 8 additions & 4 deletions pallets/funding/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,14 @@ pub mod storage_types {
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct ProjectDetails<
AccountId,
DID,
Did,
BlockNumber,
Price: FixedPointNumber,
Balance: BalanceT,
EvaluationRoundInfo,
> {
pub issuer_account: AccountId,
pub issuer_did: DID,
pub issuer_did: Did,
/// Whether the project is frozen, so no `metadata` changes are allowed.
pub is_frozen: bool,
/// The price in USD per token decided after the Auction Round
Expand Down Expand Up @@ -306,6 +306,7 @@ pub mod storage_types {
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct BidInfo<
ProjectId,
Did,
Balance: BalanceT,
Price: FixedPointNumber,
AccountId,
Expand All @@ -316,6 +317,7 @@ pub mod storage_types {
pub id: u32,
pub project_id: ProjectId,
pub bidder: AccountId,
pub did: Did,
pub status: BidStatus<Balance>,
#[codec(compact)]
pub original_ct_amount: Balance,
Expand All @@ -335,13 +337,14 @@ pub mod storage_types {

impl<
ProjectId: Eq,
Did: Eq,
Balance: BalanceT + FixedPointOperand + Ord,
Price: FixedPointNumber,
AccountId: Eq,
BlockNumber: Eq + Ord,
Multiplier: Eq,
VestingInfo: Eq,
> Ord for BidInfo<ProjectId, Balance, Price, AccountId, BlockNumber, Multiplier, VestingInfo>
> Ord for BidInfo<ProjectId, Did, Balance, Price, AccountId, BlockNumber, Multiplier, VestingInfo>
{
fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering {
match self.original_ct_usd_price.cmp(&other.original_ct_usd_price) {
Expand All @@ -353,13 +356,14 @@ pub mod storage_types {

impl<
ProjectId: Eq,
Did: Eq,
Balance: BalanceT + FixedPointOperand,
Price: FixedPointNumber,
AccountId: Eq,
BlockNumber: Eq + Ord,
Multiplier: Eq,
VestingInfo: Eq,
> PartialOrd for BidInfo<ProjectId, Balance, Price, AccountId, BlockNumber, Multiplier, VestingInfo>
> PartialOrd for BidInfo<ProjectId, Did, Balance, Price, AccountId, BlockNumber, Multiplier, VestingInfo>
{
fn partial_cmp(&self, other: &Self) -> Option<sp_std::cmp::Ordering> {
Some(self.cmp(other))
Expand Down