Skip to content

Commit e8e0538

Browse files
authored
✨ Auction Round Improvements (#447)
## What? Improve the auction system to achieve the following goals: - No limit on the amount of bids per project or per user - Early releases of failed bids so they can bid again - Make extrinsics as light-weight as possible - Avoid limiting min and max tickets ## Why? - We removed the other funding rounds and were left with only auction. - We also were using a bucket system when the initial system was designed for a free pricing auction - It was time to go back to the drawing board and see if we could optimize things ## How? ### Mark I We achieved infinite bids, but had to bound ticket sizes so the total amount of winning bids was known. We would process only the winning bids in the funding end transition and store the cutoff. Any bids after the cutoff were assumed rejected. Main problem was that you couldn't release your bid early to bid again if it was rejected. ### Mark II Infinite bids, and less strict bounded ticket sizes. Instead of processing the winning bids at the end of a project, each bidder would need to process the bids they would kick out at the moment of bidding. Any bids not processed were assumed accepted. Here we now allowed releasing your bid early since the processing was done after each bid and not at the end. But still the ticket sizes were too strict (min 100USD max 100k USD) because we had to limit the total number of bids one had to process when oversubscribed. ### Mark III Here we solved both the ticket sizes (min 10, max unlimited), and the releasing bid early. Bid extrinsics were now very light weight as well. We now processed bids that were outbid in a separate extrinsic which has to run during the auction round. This is called either by a user, or by the on-idle hook. Every new bid made from the second bucket onwards was considered to be outbidding an existing bid, so we stored that amount in storage, and then a new extrinsic would look at this storage and if theres any tokens outbid, start reading the bids in order of rejection. To know the order, we have a new map storing the bucket price to starting and ending bid id. The processing goes from lowest to highest bucket price, and inside the bucket from highest bid index to lowest. ## Testing? Several tests written and benchmarks modified.
2 parents 7274aff + 391419e commit e8e0538

34 files changed

+1328
-1724
lines changed

integration-tests/src/tests/ct_migration.rs

+13-16
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ fn get_migrations_for_participants(
7070
for participant in participants {
7171
let (status, migrations) =
7272
pallet_funding::UserMigrations::<PolimecRuntime>::get((project_id, participant.clone())).unwrap();
73-
user_migrations.insert(participant, (status, Migrations::from(migrations.into())));
73+
user_migrations.insert(participant, (status, Migrations::from(migrations.to_vec())));
7474
}
7575
});
7676
user_migrations
@@ -159,8 +159,8 @@ fn create_settled_project() -> (ProjectId, Vec<AccountId>) {
159159
let mut inst = IntegrationInstantiator::new(None);
160160

161161
let project_metadata = default_project_metadata(ISSUER.into());
162-
let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5);
163-
let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8);
162+
let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10);
163+
let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30);
164164
PolimecNet::execute_with(|| {
165165
let project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids);
166166
assert_eq!(
@@ -170,11 +170,7 @@ fn create_settled_project() -> (ProjectId, Vec<AccountId>) {
170170
let mut participants: Vec<AccountId> =
171171
pallet_funding::Evaluations::<PolimecRuntime>::iter_prefix_values((project_id,))
172172
.map(|eval| eval.evaluator)
173-
.chain(pallet_funding::Bids::<PolimecRuntime>::iter_prefix_values((project_id,)).map(|bid| bid.bidder))
174-
.chain(
175-
pallet_funding::Contributions::<PolimecRuntime>::iter_prefix_values((project_id,))
176-
.map(|contribution| contribution.contributor),
177-
)
173+
.chain(pallet_funding::Bids::<PolimecRuntime>::iter_prefix_values(project_id).map(|bid| bid.bidder))
178174
.collect();
179175
participants.sort();
180176
participants.dedup();
@@ -211,8 +207,8 @@ fn create_project_with_unsettled_participation(participation_type: Participation
211207
let mut inst = IntegrationInstantiator::new(None);
212208
PolimecNet::execute_with(|| {
213209
let project_metadata = default_project_metadata(ISSUER.into());
214-
let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5);
215-
let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8);
210+
let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10);
211+
let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30);
216212
let project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids);
217213

218214
assert_eq!(
@@ -221,12 +217,12 @@ fn create_project_with_unsettled_participation(participation_type: Participation
221217
);
222218
let evaluations_to_settle =
223219
pallet_funding::Evaluations::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();
224-
let bids_to_settle = pallet_funding::Bids::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();
220+
let bids_to_settle = inst.get_bids(project_id);
225221

226222
let mut participants: Vec<AccountId> = evaluations_to_settle
227223
.iter()
228224
.map(|eval| eval.evaluator.clone())
229-
.chain(bids_to_settle.iter().map(|bid| bid.bidder.clone()))
225+
.chain(bids_to_settle.iter().map(|x| x.bidder.clone()))
230226
.collect();
231227
participants.sort();
232228
participants.dedup();
@@ -242,14 +238,15 @@ fn create_project_with_unsettled_participation(participation_type: Participation
242238
.unwrap()
243239
}
244240

245-
let start = if participation_type == ParticipationType::Bid { 1 } else { 0 };
246-
for bid in bids_to_settle[start..].iter() {
247-
PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), project_id, bid.bidder.clone(), bid.id).unwrap()
241+
let proposed_start = if participation_type == ParticipationType::Bid { 1 } else { 0 };
242+
let end = if proposed_start == 1 { bids_to_settle.len() - 1 } else { bids_to_settle.len() };
243+
for bid in bids_to_settle[..end].iter() {
244+
PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), project_id, bid.id).unwrap()
248245
}
249246

250247
let evaluations =
251248
pallet_funding::Evaluations::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();
252-
let bids = pallet_funding::Bids::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();
249+
let bids = pallet_funding::Bids::<PolimecRuntime>::iter_prefix_values(project_id).collect_vec();
253250

254251
if participation_type == ParticipationType::Evaluation {
255252
assert_eq!(evaluations.len(), 1);

integration-tests/src/tests/defaults.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub fn default_project_metadata(issuer: AccountId) -> ProjectMetadataOf<polimec_
6868
bidding_ticket_sizes: BiddingTicketSizes {
6969
professional: TicketSize::new(5000 * USD_UNIT, None),
7070
institutional: TicketSize::new(5000 * USD_UNIT, None),
71-
retail: TicketSize::new(10 * USD_UNIT, None),
71+
retail: TicketSize::new(100 * USD_UNIT, None),
7272
phantom: Default::default(),
7373
},
7474
participation_currencies: vec![USDT, USDC, DOT, WETH].try_into().unwrap(),

integration-tests/src/tests/e2e.rs

+3-13
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,6 @@ fn participate_with_checks(
266266
mut inst: IntegrationInstantiator,
267267
project_id: ProjectId,
268268
participation_type: ParticipationType,
269-
participation_id: u32,
270269
user: [u8; 32],
271270
mode: ParticipationMode,
272271
investor_type: InvestorType,
@@ -311,9 +310,6 @@ fn participate_with_checks(
311310
if participation_type == ParticipationType::Bid {
312311
PolimecFunding::bid(PolimecOrigin::signed(user.clone()), user_jwt, project_id, ct_amount, mode, funding_asset)
313312
.unwrap();
314-
let stored_bid = Bids::<PolimecRuntime>::get((project_id, user.clone(), participation_id));
315-
// dbg!(&stored_bid);
316-
assert!(stored_bid.is_some());
317313
}
318314

319315
let post_participation_free_plmc = PolimecBalances::free_balance(user.clone());
@@ -392,14 +388,13 @@ fn e2e_test() {
392388

393389
assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound);
394390

395-
for (bid_id, user, mode, investor_type, ct_amount, _price, funding_asset, funding_asset_ticket, plmc_bonded) in
391+
for (_bid_id, user, mode, investor_type, ct_amount, _price, funding_asset, funding_asset_ticket, plmc_bonded) in
396392
pre_wap_bids()
397393
{
398394
inst = participate_with_checks(
399395
inst,
400396
project_id,
401397
ParticipationType::Bid,
402-
bid_id,
403398
user,
404399
mode,
405400
investor_type,
@@ -425,13 +420,8 @@ fn e2e_test() {
425420
let prev_treasury_usdt_balance = PolimecForeignAssets::balance(USDT.id(), otm_project_sub_account.clone());
426421
let prev_escrow_usdt_balance = PolimecForeignAssets::balance(USDT.id(), funding_escrow_account.clone());
427422

428-
PolimecFunding::settle_bid(
429-
PolimecOrigin::signed(rejected_bidder.clone()),
430-
project_id,
431-
rejected_bidder.clone(),
432-
rejected_bid_id,
433-
)
434-
.unwrap();
423+
PolimecFunding::settle_bid(PolimecOrigin::signed(rejected_bidder.clone()), project_id, rejected_bid_id)
424+
.unwrap();
435425

436426
let post_bid_free_plmc = PolimecBalances::free_balance(rejected_bidder.clone());
437427
let post_bid_reserved_plmc = PolimecBalances::reserved_balance(rejected_bidder.clone());

integration-tests/src/tests/oracle.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ fn pallet_funding_works() {
131131
assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(charlie.clone()), values([4.84, 1.0, 1.0, 2500.0, 0.4])));
132132

133133
let project_metadata = default_project_metadata(ISSUER.into());
134-
let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5);
135-
let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8);
134+
let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10);
135+
let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30);
136136
let _project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids);
137137
});
138138
}

integration-tests/src/tests/runtime_apis.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use crate::{constants::*, *};
22
use assets_common::runtime_api::runtime_decl_for_fungibles_api::FungiblesApiV2;
3-
use frame_support::traits::{fungible::Mutate as FMutate, fungibles::Mutate, tokens::ConversionToAssetBalance};
3+
use frame_support::traits::{fungible::Mutate as FMutate, fungibles::Mutate};
44
use polimec_common::assets::AcceptedFundingAsset;
5-
use polimec_runtime::PLMCToAssetBalance;
65
use sp_arithmetic::FixedU128;
76
use xcm::v4::Junctions::X3;
87
use xcm_fee_payment_runtime_api::fees::runtime_decl_for_xcm_payment_api::XcmPaymentApiV1;

justfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ benchmark-pallet chain="polimec-paseo-local" pallet="pallet-dispenser":
9999
benchmark-extrinsics pallet="pallet-funding" extrinsics="*" :
100100
cargo run --features runtime-benchmarks --profile=production -p polimec-node benchmark pallet \
101101
--chain=polimec-paseo-local \
102-
--steps=10 \
103-
--repeat=5 \
102+
--steps=50 \
103+
--repeat=20 \
104104
--pallet={{ pallet }} \
105105
--no-storage-info \
106106
--no-median-slopes \

0 commit comments

Comments
 (0)