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

✅ Comprehensive community round tests (518) #229

Merged
merged 47 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
24373bf
upgrade worked
JuaniRios Apr 5, 2024
fcc5635
nit
JuaniRios Apr 8, 2024
9c98105
Merge branch 'main' into politest-0.6.3
JuaniRios Apr 8, 2024
9a6fe1a
format auction tests, remove v0-v1 funding migration due to old types…
JuaniRios Apr 8, 2024
3377f17
save
JuaniRios Apr 8, 2024
02466b7
changes
JuaniRios Apr 8, 2024
951f67d
finish tests
JuaniRios Apr 9, 2024
700fc4c
warnings
JuaniRios Apr 9, 2024
d0e9d91
Merge branch 'main' into feature/plmc-512-comprehensive-auction-round…
JuaniRios Apr 9, 2024
9908d0c
Just's comments
JuaniRios Apr 10, 2024
da0328b
Just's comments
JuaniRios Apr 10, 2024
5508ada
Just's comments
JuaniRios Apr 10, 2024
c909248
Just's comments.
JuaniRios Apr 10, 2024
b49e49d
fixed tests by changing logic
JuaniRios Apr 10, 2024
64ea69c
min ticket now uses current bucket size
JuaniRios Apr 10, 2024
1a88265
fix warnings
JuaniRios Apr 10, 2024
00c638b
simplify test
JuaniRios Apr 11, 2024
45a5fca
more bucket assets
JuaniRios Apr 11, 2024
786d028
combine tests
JuaniRios Apr 11, 2024
d7889e5
remove unnecessary test
JuaniRios Apr 11, 2024
b3e0f97
Restructure community funding tests
JuaniRios Apr 11, 2024
db8522a
save
JuaniRios Apr 11, 2024
e091939
Add constant attribute and enhance test cases
JuaniRios Apr 11, 2024
461c359
add lower bound of 100 USD to evaluations
JuaniRios Apr 11, 2024
cba3de3
move credential checks to fail mod
JuaniRios Apr 11, 2024
c82b3cc
:fire: remove evaluation over limit bench
JuaniRios Apr 15, 2024
096df9f
:bug: fix benchmark
JuaniRios Apr 15, 2024
bd7f7ec
:children_crossing: rename extrinsic param to `ct_amount`
JuaniRios Apr 16, 2024
94cefac
:white_check_mark: fix price calculation, add bidder contributor tests
JuaniRios Apr 16, 2024
1fc98cd
:white_check_mark: insufficient funds test, failing outside community…
JuaniRios Apr 16, 2024
8458ca2
:white_check_mark: all pallet tests passing, I'm happy with the paths…
JuaniRios Apr 16, 2024
e65245c
Merge branch 'main' into feature/plmc-518-comprehensive-community-rou…
JuaniRios Apr 17, 2024
3fa45a2
:bug: fix integration tests
JuaniRios Apr 17, 2024
82418b1
Merge branch 'main' into feature/plmc-518-comprehensive-community-rou…
JuaniRios Apr 17, 2024
7406a56
Merge branch 'main' into feature/plmc-518-comprehensive-community-rou…
JuaniRios Apr 17, 2024
5e87f5a
feedback
JuaniRios Apr 19, 2024
82decef
feedback
JuaniRios Apr 19, 2024
f73acbb
save
JuaniRios Apr 19, 2024
e8e27f1
feedback
JuaniRios Apr 19, 2024
ae424ed
feedback
JuaniRios Apr 19, 2024
3e0d2a6
last feedback addressing
JuaniRios Apr 19, 2024
724db25
Merge remote-tracking branch 'origin/main' into feature/plmc-518-comp…
JuaniRios Apr 22, 2024
428067d
merge
JuaniRios Apr 22, 2024
f2237a4
fmt + wap calculation change
JuaniRios Apr 22, 2024
6487ace
fix warnings
JuaniRios Apr 22, 2024
18fd306
remove test and split another
JuaniRios Apr 22, 2024
dea87cc
fix warnings, ignore test
JuaniRios Apr 22, 2024
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
42 changes: 2 additions & 40 deletions pallets/funding/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ mod benchmarks {
let project_metadata = default_project::<T>(inst.get_new_nonce(), issuer.clone());
let test_project_id = inst.create_evaluating_project(project_metadata, issuer);

let existing_evaluation = UserToUSDBalance::new(test_evaluator.clone(), (100 * US_DOLLAR).into());
let existing_evaluation = UserToUSDBalance::new(test_evaluator.clone(), (200 * US_DOLLAR).into());
let extrinsic_evaluation = UserToUSDBalance::new(test_evaluator.clone(), (1_000 * US_DOLLAR).into());
let existing_evaluations = vec![existing_evaluation; x as usize];

Expand Down Expand Up @@ -799,37 +799,6 @@ mod benchmarks {
);
}

// - We know how many iterations it does in storage
// - We know that it requires to unbond the lowest evaluation
#[benchmark]
fn evaluation_over_limit() {
// How many other evaluations the user did for that same project
let x = <T as Config>::MaxEvaluationsPerUser::get();
let (inst, project_id, extrinsic_evaluation, extrinsic_plmc_bonded, total_expected_plmc_bonded) =
evaluation_setup::<T>(x);

let jwt = get_mock_jwt(
extrinsic_evaluation.account.clone(),
InvestorType::Institutional,
generate_did_from_account(extrinsic_evaluation.account.clone()),
);
#[extrinsic_call]
evaluate(
RawOrigin::Signed(extrinsic_evaluation.account.clone()),
jwt,
project_id,
extrinsic_evaluation.usd_amount,
);

evaluation_verification::<T>(
inst,
project_id,
extrinsic_evaluation,
extrinsic_plmc_bonded,
total_expected_plmc_bonded,
);
}

fn bid_setup<T>(
existing_bids_count: u32,
do_perform_bid_calls: u32,
Expand Down Expand Up @@ -2459,7 +2428,7 @@ mod benchmarks {

let mut evaluations = (0..y.saturating_sub(1))
.map(|i| {
UserToUSDBalance::<T>::new(account::<AccountIdOf<T>>("evaluator", 0, i), (10u128 * ASSET_UNIT).into())
UserToUSDBalance::<T>::new(account::<AccountIdOf<T>>("evaluator", 0, i), (100u128 * US_DOLLAR).into())
})
.collect_vec();

Expand Down Expand Up @@ -2685,13 +2654,6 @@ mod benchmarks {
});
}

#[test]
fn bench_evaluation_over_limit() {
new_test_ext().execute_with(|| {
assert_ok!(PalletFunding::<TestRuntime>::test_evaluation_over_limit());
});
}

#[test]
fn bench_start_auction_manually() {
new_test_ext().execute_with(|| {
Expand Down
98 changes: 35 additions & 63 deletions pallets/funding/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use frame_support::{
pallet_prelude::*,
traits::{
fungible::{Mutate, MutateHold as FungibleMutateHold},
fungibles::{metadata::Mutate as MetadataMutate, Create, Mutate as FungiblesMutate},
fungibles::{metadata::Mutate as MetadataMutate, Create, Inspect, Mutate as FungiblesMutate},
tokens::{Precision, Preservation},
Get,
},
Expand Down Expand Up @@ -929,6 +929,7 @@ impl<T: Config> Pallet<T> {
let evaluations_count = EvaluationCounts::<T>::get(project_id);

// * Validity Checks *
ensure!(usd_amount >= T::MinUsdPerEvaluation::get(), Error::<T>::EvaluationBondTooLow);
ensure!(project_details.issuer_did != did, Error::<T>::ParticipationToThemselves);
ensure!(project_details.status == ProjectStatus::EvaluationRound, Error::<T>::ProjectNotInEvaluationRound);
ensure!(evaluations_count < T::MaxEvaluationsPerProject::get(), Error::<T>::TooManyEvaluationsForProject);
Expand Down Expand Up @@ -971,33 +972,7 @@ impl<T: Config> Pallet<T> {
when: now,
};

if caller_existing_evaluations.len() < T::MaxEvaluationsPerUser::get() as usize {
T::NativeCurrency::hold(&HoldReason::Evaluation(project_id).into(), evaluator, plmc_bond)?;
} else {
let (low_id, lowest_evaluation) = caller_existing_evaluations
.iter()
.min_by_key(|(_, evaluation)| evaluation.original_plmc_bond)
.ok_or(Error::<T>::ImpossibleState)?;

ensure!(lowest_evaluation.original_plmc_bond < plmc_bond, Error::<T>::EvaluationBondTooLow);
ensure!(
lowest_evaluation.original_plmc_bond == lowest_evaluation.current_plmc_bond,
"Using evaluation funds for participating should not be possible in the evaluation round"
);

T::NativeCurrency::release(
&HoldReason::Evaluation(project_id).into(),
&lowest_evaluation.evaluator,
lowest_evaluation.original_plmc_bond,
Precision::Exact,
)?;

T::NativeCurrency::hold(&HoldReason::Evaluation(project_id).into(), evaluator, plmc_bond)?;

Evaluations::<T>::remove((project_id, evaluator, low_id));
EvaluationCounts::<T>::mutate(project_id, |c| *c -= 1);
}

T::NativeCurrency::hold(&HoldReason::Evaluation(project_id).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 += usd_amount;
Expand Down Expand Up @@ -1249,7 +1224,7 @@ impl<T: Config> Pallet<T> {
let mut project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
let did_has_winning_bid = DidWithWinningBids::<T>::get(project_id, did.clone());

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

let buyable_tokens = token_amount.min(project_details.remaining_contribution_tokens);
Expand Down Expand Up @@ -1367,23 +1342,6 @@ 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 @@ -2025,14 +1983,18 @@ impl<T: Config> Pallet<T> {
let weighted_price = bid.original_ct_usd_price.saturating_mul(bid_weight);
weighted_price
};
let weighted_token_price = if is_first_bucket || accepted_bids.is_empty() {
let mut weighted_token_price = if is_first_bucket || accepted_bids.is_empty() {
project_metadata.minimum_price
} else {
accepted_bids
.iter()
.map(calc_weighted_price_fn)
.fold(Zero::zero(), |a: T::Price, b: T::Price| a.saturating_add(b))
};
// If the first bucket was sold out with rejected bids, the wap might be slightly lower than min_price due to rounding.
if weighted_token_price < project_metadata.minimum_price {
weighted_token_price = project_metadata.minimum_price;
}
JuaniRios marked this conversation as resolved.
Show resolved Hide resolved

let mut final_total_funding_reached_by_bids = BalanceOf::<T>::zero();

Expand All @@ -2055,15 +2017,20 @@ impl<T: Config> Pallet<T> {
.checked_mul_int(new_ticket_size)
.ok_or(Error::<T>::BadMath)?;

T::FundingCurrency::transfer(
bid.funding_asset.to_assethub_id(),
&project_account,
&bid.bidder,
bid.funding_asset_amount_locked.saturating_sub(funding_asset_amount_needed),
Preservation::Preserve,
)?;

bid.funding_asset_amount_locked = funding_asset_amount_needed;
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,
bid.funding_asset_amount_locked.saturating_sub(funding_asset_amount_needed),
JuaniRios marked this conversation as resolved.
Show resolved Hide resolved
Preservation::Preserve,
)?;
bid.funding_asset_amount_locked = funding_asset_amount_needed;
}

let usd_bond_needed = bid
.multiplier
Expand All @@ -2075,12 +2042,16 @@ impl<T: Config> Pallet<T> {
.checked_mul_int(usd_bond_needed)
.ok_or(Error::<T>::BadMath)?;

T::NativeCurrency::release(
&HoldReason::Participation(project_id).into(),
&bid.bidder,
bid.plmc_bond.saturating_sub(plmc_bond_needed),
Precision::Exact,
)?;
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,
)?;
}
Comment on lines +2171 to +2178
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does a release of a hold fail? We are not actually sending tokens, right? Just releasing tokens that are already in the users account?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand from a quick test, its not possible to hold funds if it would put your free balance under ED. So I think this situation should in theory not happen. Lets say we have 100 tokens with ED = 1:

  • We hold 100. -> Error
  • We hold 90 -> transfer_allow_death(10) -> Error.
    So balance should always contain at least ED free tokens in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CleanShot 2024-04-19 at 16.00.55.png

This is the logic for placing a hold. It is called with Protect and Force.
The case for erroring out when there's no ED left after hold is when there is only 1 provider. And if I understand correctly, we could in the future introduce pallets that provide for the existence of the account, making it possible for it to live without an ED.

Therefore I prefer to be safe and do a check before releasing so we don't halt the whole project. I don't think most people will be getting a return below ED anyway, and if they do they shouldn't mind it not being returned.

@lrazovic any thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CleanShot 2024-04-19 at 16.04.25.png

For reference here is the logic for releasing an asset. It always checks the amount free + the released amount will be bigger than ED.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From: paritytech/substrate#12951

"... may be replaced with fungible::hold::Mutate::release, however the returned Result must be handled and the inner of Ok has the opposite meaning (i.e. it is the amount released, rather than the amount not released, if any)."

So I don't think that release is infallible. Now I throw the question back at you: is this the best way to handle this possible failure? If yes, then we can resolve the comment.

Tagging also @joepetrowski who maybe has some extra input on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it fails due to ED, we can cover it. If it fails for some other reason we probably don't want to continue the project transition because it's unexpected behavior and we should fix it and then force run the community start.

So I think we can resolve as is


bid.plmc_bond = plmc_bond_needed;
}
Expand Down Expand Up @@ -2195,7 +2166,8 @@ impl<T: Config> Pallet<T> {
to_convert = to_convert.saturating_sub(converted)
}

T::NativeCurrency::hold(&HoldReason::Participation(project_id).into(), who, to_convert)?;
T::NativeCurrency::hold(&HoldReason::Participation(project_id).into(), who, to_convert)
.map_err(|_| Error::<T>::NotEnoughFunds)?;

Ok(())
}
Expand Down
9 changes: 5 additions & 4 deletions pallets/funding/src/instantiator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ impl<
for UserToPLMCBalance { account, plmc_amount } in correct_funds {
self.execute(|| {
let reserved = <T as Config>::NativeCurrency::balance_on_hold(&reserve_type, &account);
assert_eq!(reserved, plmc_amount, "account has unexpected reserved plmc balance");
assert_eq!(reserved, plmc_amount);
});
}
}
Expand Down Expand Up @@ -261,7 +261,7 @@ impl<
for UserToPLMCBalance { account, plmc_amount } in correct_funds {
self.execute(|| {
let free = <T as Config>::NativeCurrency::balance(&account);
assert_eq!(free, plmc_amount, "account has unexpected free plmc balance");
assert_eq!(free, plmc_amount, "account {account} has unexpected free plmc balance");
});
}
}
Expand Down Expand Up @@ -1085,7 +1085,7 @@ impl<
let evaluation_end = project_details.phase_transition_points.evaluation.end().unwrap();
let auction_start = evaluation_end.saturating_add(2u32.into());
let blocks_to_start = auction_start.saturating_sub(self.current_block());
self.advance_time(blocks_to_start).unwrap();
self.advance_time(blocks_to_start + 1u32.into()).unwrap();
};

assert_eq!(self.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod);
Expand Down Expand Up @@ -1160,8 +1160,9 @@ impl<
.expect("Auction Opening end point should exist");

self.execute(|| frame_system::Pallet::<T>::set_block_number(opening_end));

// run on_initialize
self.advance_time(1u32.into()).unwrap();
self.advance_time(2u32.into()).unwrap();

let closing_end = self
.get_project_details(project_id)
Expand Down
8 changes: 6 additions & 2 deletions pallets/funding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,12 @@ pub mod pallet {
type MaxEvaluationsPerProject: Get<u32>;

/// How many distinct evaluations per user per project
#[pallet::constant]
type MaxEvaluationsPerUser: Get<u32>;

#[pallet::constant]
type MinUsdPerEvaluation: Get<BalanceOf<Self>>;

/// Range of max_message_size values for the hrmp config where we accept the incoming channel request
#[pallet::constant]
type MaxMessageSizeThresholds: Get<(u32, u32)>;
Expand Down Expand Up @@ -916,13 +920,13 @@ pub mod pallet {
origin: OriginFor<T>,
jwt: UntrustedToken,
project_id: ProjectId,
#[pallet::compact] amount: BalanceOf<T>,
#[pallet::compact] ct_amount: BalanceOf<T>,
multiplier: T::Multiplier,
asset: AcceptedFundingAsset,
) -> DispatchResultWithPostInfo {
let (account, did, investor_type) =
T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?;
Self::do_bid(&account, project_id, amount, multiplier, asset, did, investor_type)
Self::do_bid(&account, project_id, ct_amount, multiplier, asset, did, investor_type)
}

/// Buy tokens in the Community or Remainder round at the price set in the Auction Round
Expand Down
2 changes: 2 additions & 0 deletions pallets/funding/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ parameter_types! {
32, 118, 30, 171, 58, 212, 197, 27, 146, 122, 255, 243, 34, 245, 90, 244, 221, 37, 253,
195, 18, 202, 111, 55, 39, 48, 123, 17, 101, 78, 215, 94,
];
pub MinUsdPerEvaluation: Balance = 100 * US_DOLLAR;
}

pub struct DummyConverter;
Expand Down Expand Up @@ -426,6 +427,7 @@ impl Config for TestRuntime {
type MaxMessageSizeThresholds = MaxMessageSizeThresholds;
type MaxProjectsToUpdateInsertionAttempts = ConstU32<100>;
type MaxProjectsToUpdatePerBlock = ConstU32<1>;
type MinUsdPerEvaluation = MinUsdPerEvaluation;
type Multiplier = Multiplier;
type NativeCurrency = Balances;
type PalletId = FundingPalletId;
Expand Down
40 changes: 39 additions & 1 deletion pallets/funding/src/tests/2_evaluation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ mod evaluate_extrinsic {
let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext())));
let project_metadata = default_project_metadata(ISSUER_1);
let evaluations = (0u32..<TestRuntime as Config>::MaxEvaluationsPerProject::get())
.map(|i| UserToUSDBalance::<TestRuntime>::new(i as u32 + 420u32, (10u128 * ASSET_UNIT).into()))
.map(|i| UserToUSDBalance::<TestRuntime>::new(i as u32 + 420u32, (100u128 * ASSET_UNIT).into()))
.collect_vec();
let failing_evaluation = UserToUSDBalance::new(EVALUATOR_1, 1000 * ASSET_UNIT);

Expand Down Expand Up @@ -639,5 +639,43 @@ mod evaluate_extrinsic {
);
});
}

#[test]
fn cannot_evaluate_with_less_than_100_usd() {
let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext())));
let issuer = ISSUER_1;
let project_metadata = default_project_metadata(issuer);
let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer);
let evaluator = EVALUATOR_1;
let jwt = get_mock_jwt(evaluator, InvestorType::Retail, generate_did_from_account(evaluator));

inst.mint_plmc_to(vec![(evaluator.clone(), 2000 * PLMC).into()]);

// Cannot evaluate with 0 USD
inst.execute(|| {
assert_noop!(
Pallet::<TestRuntime>::evaluate(
RuntimeOrigin::signed(evaluator.clone()),
jwt.clone(),
project_id,
0
),
Error::<TestRuntime>::EvaluationBondTooLow
);
});

// Cannot evaluate with less than 99 USD
inst.execute(|| {
assert_noop!(
Pallet::<TestRuntime>::evaluate(
RuntimeOrigin::signed(evaluator.clone()),
jwt.clone(),
project_id,
99 * US_DOLLAR
),
Error::<TestRuntime>::EvaluationBondTooLow
);
});
}
}
}
Loading