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 45 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 @@ -675,7 +675,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 @@ -792,37 +792,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 @@ -2456,7 +2425,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 @@ -2682,13 +2651,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
117 changes: 43 additions & 74 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 @@ -979,6 +979,10 @@ impl<T: Config> Pallet<T> {
let evaluations_count = EvaluationCounts::<T>::get(project_id);

// * Validity Checks *
ensure!(
usd_amount >= T::MinUsdPerEvaluation::get(),
Error::<T>::ParticipationFailed(ParticipationError::TooLow)
);
ensure!(
project_details.issuer_did != did,
Error::<T>::IssuerError(IssuerErrorReason::ParticipationToOwnProject)
Expand Down Expand Up @@ -1030,36 +1034,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>::ParticipationFailed(ParticipationError::TooLow)
);
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 @@ -1464,26 +1439,6 @@ impl<T: Config> Pallet<T> {
contributor_ticket_size.usd_ticket_below_maximum_per_did(total_usd_bought_by_did + ticket_size),
Error::<T>::ParticipationFailed(ParticipationError::TooHigh)
);
ensure!(
project_metadata.participation_currencies.contains(&funding_asset),
Error::<T>::ParticipationFailed(ParticipationError::FundingAssetNotAccepted)
);
ensure!(
did.clone() != project_details.issuer_did,
Error::<T>::IssuerError(IssuerErrorReason::ParticipationToOwnProject)
);
ensure!(
caller_existing_contributions.len() < T::MaxContributionsPerUser::get() as usize,
Error::<T>::ParticipationFailed(ParticipationError::TooManyUserParticipations)
);
ensure!(
contributor_ticket_size.usd_ticket_above_minimum_per_participation(ticket_size),
Error::<T>::ParticipationFailed(ParticipationError::TooLow)
);
ensure!(
contributor_ticket_size.usd_ticket_below_maximum_per_did(total_usd_bought_by_did + ticket_size),
Error::<T>::ParticipationFailed(ParticipationError::TooHigh)
);

let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier, plmc_usd_price)?;
let funding_asset_amount =
Expand Down Expand Up @@ -2074,6 +2029,9 @@ impl<T: Config> Pallet<T> {
let mut bid_usd_value_sum = BalanceOf::<T>::zero();
let project_account = Self::fund_account_id(project_id);
let plmc_price = T::PriceProvider::get_price(PLMC_FOREIGN_ID).ok_or(Error::<T>::PriceNotFound)?;
let project_metadata = ProjectsMetadata::<T>::get(project_id)
.ok_or(Error::<T>::ProjectError(ProjectErrorReason::ProjectMetadataNotFound))?;
let mut highest_accepted_price = project_metadata.minimum_price;

// sort bids by price, and equal prices sorted by id
bids.sort_by(|a, b| b.cmp(a));
Expand All @@ -2097,6 +2055,7 @@ impl<T: Config> Pallet<T> {
DidWithWinningBids::<T>::mutate(project_id, bid.did.clone(), |flag| {
*flag = true;
});
highest_accepted_price = highest_accepted_price.max(bid.original_ct_usd_price);
} else {
let ticket_size = bid.original_ct_usd_price.saturating_mul_int(buyable_amount);
bid_usd_value_sum.saturating_accrue(ticket_size);
Expand All @@ -2106,6 +2065,7 @@ impl<T: Config> Pallet<T> {
*flag = true;
});
bid.final_ct_amount = buyable_amount;
highest_accepted_price = highest_accepted_price.max(bid.original_ct_usd_price);
}
bid
})
Expand Down Expand Up @@ -2140,26 +2100,25 @@ impl<T: Config> Pallet<T> {

// lastly, sum all the weighted prices to get the final weighted price for the next funding round
// 3 + 10.6 + 2.6 = 16.333...
let current_bucket =
Buckets::<T>::get(project_id).ok_or(Error::<T>::ProjectError(ProjectErrorReason::BucketNotFound))?;
let project_metadata = ProjectsMetadata::<T>::get(project_id)
.ok_or(Error::<T>::ProjectError(ProjectErrorReason::ProjectMetadataNotFound))?;
let is_first_bucket = current_bucket.current_price == project_metadata.minimum_price;

let calc_weighted_price_fn = |bid: &BidInfoOf<T>| -> PriceOf<T> {
let ticket_size = bid.original_ct_usd_price.saturating_mul_int(bid.final_ct_amount);
let bid_weight = <T::Price as FixedPointNumber>::saturating_from_rational(ticket_size, bid_usd_value_sum);
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 highest_accepted_price == project_metadata.minimum_price {
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))
};
// We are 99% sure that the price cannot be less than minimum if some accepted bids have higher price, but rounding
// errors are strange, so we keep this just in case.
if weighted_token_price < project_metadata.minimum_price {
weighted_token_price = project_metadata.minimum_price;
}

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

Expand All @@ -2182,15 +2141,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,
amount_returned,
Preservation::Preserve,
)?;
bid.funding_asset_amount_locked = funding_asset_amount_needed;
}

let usd_bond_needed = bid
.multiplier
Expand All @@ -2202,12 +2166,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 @@ -2322,7 +2290,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>::ParticipationFailed(ParticipationError::NotEnoughFunds))?;

Ok(())
}
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 @@ -431,7 +431,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 @@ -507,7 +507,7 @@ impl<

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
2 changes: 0 additions & 2 deletions pallets/funding/src/instantiator/macros.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use super::*;

#[macro_export]
/// Example:
/// ```
Expand Down
1 change: 1 addition & 0 deletions pallets/funding/src/instantiator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ use sp_std::{
};

pub mod macros;
#[cfg(any(test, feature = "std", feature = "runtime-benchmarks"))]
pub use macros::*;
pub mod types;
pub use types::*;
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 @@ -822,13 +826,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
Loading