Skip to content

Commit 5208715

Browse files
authored
🚮 Remove WAP (#449)
## What? - Remove the WAP - Accepted bids don't get any refund since there's no difference between price paid and final price. - Only Partially accepted bids get some refund - Rejected bids still get full refund ## Why? - No contribution round to profit from the WAP, so only thing we're doing is making the system more confusing and giving less funds to the issuer. ## How? - Remove all references to the WAP - Don't give refunds based on the price difference ## Testing? - Delete WAP related tests - Amend the other tests
2 parents e8e0538 + 2e03b9d commit 5208715

17 files changed

+188
-1060
lines changed

integration-tests/src/tests/e2e.rs

+19-33
Original file line numberDiff line numberDiff line change
@@ -158,24 +158,20 @@ fn pre_wap_bids() -> Vec<(u32, [u8; 32], ParticipationMode, InvestorType, u64, f
158158
]
159159
}
160160

161-
fn wap() -> f64 {
162-
10.30346708
163-
}
164-
165161
#[allow(unused)]
166162
fn post_wap_bids() -> Vec<(u32, [u8; 32], ParticipationMode, InvestorType, u64, f64, AcceptedFundingAsset, f64, f64)> {
167163
// (bid_id, User, Participation mode, Investor type, CTs specified in extrinsic, CT Price, Participation Currency, Final participation currency ticket, PLMC bonded as a consequence)
168164
vec![
169-
(21, SAM, OTM, Professional, 2_000, 10.303467, USDT, 20_916.0382, 22_620.1253),
170-
(22, SAM, OTM, Professional, 2_200, 10.303467, DOT, 4_947.8800, 24_882.1378),
171-
(16, DAVE, OTM, Professional, 1_000, 10.303467, USDT, 10_458.0191, 11_310.0627),
172-
(17, GERALT, Classic(15), Institutional, 500, 10.303467, USDT, 5_151.7335, 1_885.0104),
173-
(18, GEORGE, Classic(20), Institutional, 1_900, 10.303467, USDT, 19_576.5875, 5_372.2798),
174-
(19, GINO, Classic(25), Institutional, 600, 10.303467, USDT, 6_182.0802, 1_357.2075),
175-
(20, STEVE, OTM, Professional, 1_000, 10.303467, USDT, 10_458.0191, 11_310.0627),
176-
(0, BROCK, OTM, Professional, 700, 10.000000, USDC, 7_101.4493, 7_683.8639),
177-
(1, BEN, OTM, Professional, 4_000, 10.000000, USDT, 40_600.0000, 43_907.7936),
178-
(2, BILL, Classic(3), Professional, 3_000, 10.000000, USDC, 29_985.0075, 54_884.7420),
165+
(21, SAM, OTM, Professional, 2_000, 12.0, USDT, 24000.0, 26344.6762),
166+
(22, SAM, OTM, Professional, 2_200, 12.0, DOT, 5677.419355, 28979.1438),
167+
(16, DAVE, OTM, Professional, 1_000, 11.0, USDT, 11000.0, 12074.6432),
168+
(17, GERALT, Classic(15), Institutional, 500, 11.0, USDT, 5500.0, 2012.4405),
169+
(18, GEORGE, Classic(20), Institutional, 1_900, 11.0, USDT, 20900.0, 5735.4555),
170+
(19, GINO, Classic(25), Institutional, 600, 11.0, USDT, 6600.0, 1448.9572),
171+
(20, STEVE, OTM, Professional, 1_000, 11.0, USDT, 11000.0, 12074.6432),
172+
(0, BROCK, OTM, Professional, 700, 10.000000, USDC, 6996.501749, 7_683.8639),
173+
(1, BEN, OTM, Professional, 4_000, 10.000000, USDT, 40_000.0, 43_907.7936),
174+
(2, BILL, Classic(3), Professional, 3_000, 10.000000, USDC, 29985.0075, 54_884.7420),
179175
(3, BRAD, Classic(6), Professional, 700, 10.000000, USDT, 7_000.0000, 6_403.2199),
180176
(4, BROCK, Classic(9), Professional, 3_400, 10.000000, USDT, 34_000.0000, 20_734.2359),
181177
(5, BLAIR, Classic(8), Professional, 1_000, 10.000000, USDT, 10_000.0000, 6_860.5928),
@@ -187,7 +183,7 @@ fn post_wap_bids() -> Vec<(u32, [u8; 32], ParticipationMode, InvestorType, u64,
187183
(11, BELLA, Classic(1), Professional, 800, 10.000000, USDT, 8_000.0000, 43_907.7936),
188184
(12, BRUCE, Classic(4), Institutional, 3_000, 10.000000, USDT, 30_000.0000, 41_163.5565),
189185
(13, BRENT, Classic(1), Institutional, 8_000, 10.000000, USDT, 80_000.0000, 439_077.9363),
190-
(14, DOUG, OTM, Institutional, 100, 10.000000, USDT, 1_015.0000, 5_488.4742),
186+
(14, DOUG, OTM, Institutional, 100, 10.000000, USDT, 1000.0, 5_488.4742),
191187
(15, DAVE, OTM, Professional, 0, 10.000000, USDT, 0.00, 0.00),
192188
]
193189
}
@@ -198,7 +194,7 @@ fn cts_minted() -> f64 {
198194
}
199195

200196
fn usd_raised() -> f64 {
201-
502_791.8972
197+
513_400.0000
202198
}
203199

204200
#[allow(unused)]
@@ -209,7 +205,7 @@ fn ct_fees() -> (f64, f64, f64) {
209205

210206
fn issuer_payouts() -> (f64, f64, f64) {
211207
// (USDT, USDC, DOT)
212-
(430_124.27, 36_981.51, 7_670.46)
208+
(437_000.00, 36_981.51, 8_473.12)
213209
}
214210

215211
fn evaluator_reward_pots() -> (f64, f64, f64, f64) {
@@ -245,17 +241,17 @@ fn final_payouts() -> Vec<([u8; 32], f64, f64)> {
245241
(DAVE, 1059.661749, 0.00),
246242
(MASON, 35.37757672, 0.00),
247243
(MIKE, 82.74302164, 0.00),
248-
(GERALT, 500.0, 1_885.01),
249-
(GEORGE, 1_900.0, 5_372.28),
250-
(GINO, 600.0, 1_357.21),
244+
(GERALT, 500.0, 2_012.44),
245+
(GEORGE, 1_900.0, 5_735.46),
246+
(GINO, 600.0, 1_448.96),
251247
(STEVE, 1017.159307, 0.0),
252248
(SAM, 4267.09505, 0.0),
253249
]
254250
}
255251

256252
fn otm_fee_recipient_balances() -> (f64, f64, f64) {
257253
// USDT, USDC, DOT
258-
(1233.208025, 104.9475262, 73.12137929)
254+
(1305.0, 104.9475262, 85.16129032)
259255
}
260256

261257
fn otm_treasury_sub_account_plmc_held() -> f64 {
@@ -462,18 +458,6 @@ fn e2e_test() {
462458

463459
let project_details = inst.get_project_details(project_id);
464460
let project_metadata = inst.get_project_metadata(project_id);
465-
let stored_wap = project_details.weighted_average_price.unwrap();
466-
let expected_wap = PriceProviderOf::<PolimecRuntime>::calculate_decimals_aware_price(
467-
PriceOf::<PolimecRuntime>::from_float(wap()),
468-
USD_DECIMALS,
469-
CT_DECIMALS,
470-
)
471-
.unwrap();
472-
assert_close_enough!(
473-
stored_wap.saturating_mul_int(PLMC),
474-
expected_wap.saturating_mul_int(PLMC),
475-
Perquintill::from_float(0.9999)
476-
);
477461

478462
let actual_cts_minted = polimec_runtime::ContributionTokens::total_issuance(project_id);
479463
let expected_cts_minted = FixedU128::from_float(cts_minted()).saturating_mul_int(CT_UNIT);
@@ -544,6 +528,8 @@ fn e2e_test() {
544528
);
545529

546530
for (user, ct_rewarded, plmc_bonded) in final_payouts() {
531+
let names = names();
532+
547533
let user: PolimecAccountId = user.into();
548534
let ct_rewarded = FixedU128::from_float(ct_rewarded).saturating_mul_int(CT_UNIT);
549535
let plmc_bonded = FixedU128::from_float(plmc_bonded).saturating_mul_int(PLMC);

pallets/funding/src/benchmarking.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -771,8 +771,8 @@ mod benchmarks {
771771
}
772772

773773
// We have 3 logic paths
774-
// 1 - Accepted bid with no refunds (i.e. final price <= WAP, no partial acceptance)
775-
// 2 - Accepted bid with refund (i.e. final price > WAP or partial acceptance)
774+
// 1 - Accepted bid with no refunds
775+
// 2 - Accepted bid with refund (i.e. partially accepted bid )
776776
// 3 - Rejected bid (i.e. bid not accepted, everything refunded, no CT/migration)
777777
// Path 2 is the most expensive but not by far, so we only benchmark and charge for this weight
778778
#[benchmark]
@@ -788,10 +788,14 @@ mod benchmarks {
788788

789789
let project_metadata = default_project_metadata::<T>(issuer.clone());
790790
let increase = project_metadata.minimum_price * PriceOf::<T>::saturating_from_rational(5, 10);
791-
let target_wap = project_metadata.minimum_price + increase;
791+
let target_price = project_metadata.minimum_price + increase;
792+
793+
let mut new_bucket = Pallet::<T>::create_bucket_from_metadata(&project_metadata).unwrap();
794+
new_bucket.current_price = target_price;
795+
new_bucket.amount_left = new_bucket.delta_amount;
792796

793797
let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10);
794-
let bids = inst.generate_bids_that_take_price_to(project_metadata.clone(), target_wap);
798+
let bids = inst.generate_bids_from_bucket(project_metadata.clone(), new_bucket, USDT);
795799

796800
let project_id =
797801
inst.create_finished_project(project_metadata.clone(), issuer, None, evaluations, bids.clone());
@@ -829,7 +833,6 @@ mod benchmarks {
829833
id: bid_to_settle.id,
830834
status: bid_to_settle.status,
831835
final_ct_amount: expected_ct_amount,
832-
final_ct_usd_price: bid_to_settle.original_ct_usd_price,
833836
}
834837
.into(),
835838
);

pallets/funding/src/functions/1_application.rs

-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ impl<T: Config> Pallet<T> {
3333
issuer_account: issuer.clone(),
3434
issuer_did: did.clone(),
3535
is_frozen: false,
36-
weighted_average_price: None,
3736
fundraising_target_usd: fundraising_target,
3837
status: ProjectStatus::Application,
3938
round_duration: BlockNumberPair::new(None, None),

pallets/funding/src/functions/5_funding_end.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ impl<T: Config> Pallet<T> {
2626
ProjectsInAuctionRound::<T>::put(WeakBoundedVec::force_from(project_ids, None));
2727

2828
let auction_allocation_size = project_metadata.total_allocation_size;
29-
let weighted_average_price = bucket.calculate_wap(auction_allocation_size);
30-
project_details.weighted_average_price = Some(weighted_average_price);
3129

3230
let bucket_price_higher_than_initial = bucket.current_price > bucket.initial_price;
3331
let sold_percent =
@@ -39,7 +37,7 @@ impl<T: Config> Pallet<T> {
3937

4038
DidWithActiveProjects::<T>::set(issuer_did, None);
4139

42-
let usd_raised = Self::calculate_usd_sold_from_bucket(bucket.clone(), project_metadata.total_allocation_size);
40+
let usd_raised = bucket.calculate_usd_raised(auction_allocation_size);
4341
project_details.funding_amount_reached_usd = usd_raised;
4442
project_details.remaining_contribution_tokens =
4543
if bucket.current_price == bucket.initial_price { bucket.amount_left } else { Zero::zero() };

pallets/funding/src/functions/6_settlement.rs

+23-27
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ impl<T: Config> Pallet<T> {
159159
let project_metadata = ProjectsMetadata::<T>::get(project_id).ok_or(Error::<T>::ProjectMetadataNotFound)?;
160160
let funding_success =
161161
matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::Success));
162-
let wap = project_details.weighted_average_price.unwrap_or(project_metadata.minimum_price);
163162
let mut bid = Bids::<T>::get(project_id, bid_id).ok_or(Error::<T>::ParticipationNotFound)?;
164163

165164
ensure!(
@@ -173,8 +172,8 @@ impl<T: Config> Pallet<T> {
173172

174173
// Return the full bid amount to refund if bid is rejected or project failed,
175174
// Return a partial amount if the project succeeded, and the wap > paid price or bid is partially accepted
176-
let BidRefund { final_ct_usd_price, final_ct_amount, refunded_plmc, refunded_funding_asset_amount } =
177-
Self::calculate_refund(&bid, funding_success, wap)?;
175+
let BidRefund { final_ct_amount, refunded_plmc, refunded_funding_asset_amount } =
176+
Self::calculate_refund(&bid, funding_success)?;
178177

179178
Self::release_funding_asset(project_id, &bid.bidder, refunded_funding_asset_amount, bid.funding_asset)?;
180179

@@ -227,42 +226,39 @@ impl<T: Config> Pallet<T> {
227226
id: bid.id,
228227
status: bid.status,
229228
final_ct_amount,
230-
final_ct_usd_price,
231229
});
232230

233231
Ok(())
234232
}
235233

236234
/// Calculate the amount of funds the bidder should receive back based on the original bid
237235
/// amount and price compared to the final bid amount and price.
238-
fn calculate_refund(
239-
bid: &BidInfoOf<T>,
240-
funding_success: bool,
241-
wap: PriceOf<T>,
242-
) -> Result<BidRefund<T>, DispatchError> {
243-
let final_ct_usd_price = if bid.original_ct_usd_price > wap { wap } else { bid.original_ct_usd_price };
236+
fn calculate_refund(bid: &BidInfoOf<T>, funding_success: bool) -> Result<BidRefund, DispatchError> {
244237
let multiplier: MultiplierOf<T> = bid.mode.multiplier().try_into().map_err(|_| Error::<T>::BadMath)?;
245-
if !funding_success || bid.status == BidStatus::Rejected {
246-
return Ok(BidRefund::<T> {
247-
final_ct_usd_price,
238+
let ct_price = bid.original_ct_usd_price;
239+
240+
match bid.status {
241+
BidStatus::Accepted if funding_success => Ok(BidRefund {
242+
final_ct_amount: bid.original_ct_amount,
243+
refunded_plmc: Zero::zero(),
244+
refunded_funding_asset_amount: Zero::zero(),
245+
}),
246+
BidStatus::PartiallyAccepted(accepted_amount) if funding_success => {
247+
let new_ticket_size = ct_price.checked_mul_int(accepted_amount).ok_or(Error::<T>::BadMath)?;
248+
let new_plmc_bond = Self::calculate_plmc_bond(new_ticket_size, multiplier)?;
249+
let new_funding_asset_amount =
250+
Self::calculate_funding_asset_amount(new_ticket_size, bid.funding_asset)?;
251+
let refunded_plmc = bid.plmc_bond.saturating_sub(new_plmc_bond);
252+
let refunded_funding_asset_amount =
253+
bid.funding_asset_amount_locked.saturating_sub(new_funding_asset_amount);
254+
Ok(BidRefund { final_ct_amount: accepted_amount, refunded_plmc, refunded_funding_asset_amount })
255+
},
256+
_ => Ok(BidRefund {
248257
final_ct_amount: Zero::zero(),
249258
refunded_plmc: bid.plmc_bond,
250259
refunded_funding_asset_amount: bid.funding_asset_amount_locked,
251-
});
260+
}),
252261
}
253-
let final_ct_amount = match bid.status {
254-
BidStatus::Accepted => bid.original_ct_amount,
255-
BidStatus::PartiallyAccepted(accepted_amount) => accepted_amount,
256-
_ => Zero::zero(),
257-
};
258-
259-
let new_ticket_size = final_ct_usd_price.checked_mul_int(final_ct_amount).ok_or(Error::<T>::BadMath)?;
260-
let new_plmc_bond = Self::calculate_plmc_bond(new_ticket_size, multiplier)?;
261-
let new_funding_asset_amount = Self::calculate_funding_asset_amount(new_ticket_size, bid.funding_asset)?;
262-
let refunded_plmc = bid.plmc_bond.saturating_sub(new_plmc_bond);
263-
let refunded_funding_asset_amount = bid.funding_asset_amount_locked.saturating_sub(new_funding_asset_amount);
264-
265-
Ok(BidRefund::<T> { final_ct_usd_price, final_ct_amount, refunded_plmc, refunded_funding_asset_amount })
266262
}
267263

268264
pub fn do_mark_project_as_settled(project_id: ProjectId) -> DispatchResult {

pallets/funding/src/functions/misc.rs

-38
Original file line numberDiff line numberDiff line change
@@ -456,44 +456,6 @@ impl<T: Config> Pallet<T> {
456456
let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id.clone());
457457
<PriceProviderOf<T>>::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals)
458458
}
459-
460-
pub fn calculate_usd_sold_from_bucket(mut bucket: BucketOf<T>, auction_allocation_size: Balance) -> Balance {
461-
if bucket.current_price == bucket.initial_price {
462-
return bucket.initial_price.saturating_mul_int(auction_allocation_size.saturating_sub(bucket.amount_left))
463-
}
464-
465-
let mut total_usd_sold = 0u128;
466-
let wap = bucket.calculate_wap(auction_allocation_size);
467-
468-
let mut total_ct_amount_left = auction_allocation_size;
469-
470-
// Latest bucket will be partially sold
471-
let ct_sold = bucket.delta_amount.saturating_sub(bucket.amount_left);
472-
let usd_sold = wap.saturating_mul_int(ct_sold);
473-
total_usd_sold = total_usd_sold.saturating_add(usd_sold);
474-
total_ct_amount_left = total_ct_amount_left.saturating_sub(ct_sold);
475-
bucket.current_price = bucket.current_price.saturating_sub(bucket.delta_price);
476-
477-
while total_ct_amount_left > 0 {
478-
// If we reached the inital bucket, all the CTs remaining are taken from this bucket
479-
if bucket.current_price == bucket.initial_price {
480-
let ct_sold = total_ct_amount_left;
481-
let usd_sold = bucket.initial_price.saturating_mul_int(ct_sold);
482-
total_usd_sold = total_usd_sold.saturating_add(usd_sold);
483-
break
484-
}
485-
486-
let price_charged = wap.min(bucket.current_price);
487-
let ct_sold = total_ct_amount_left.min(bucket.delta_amount);
488-
let usd_raised = price_charged.saturating_mul_int(ct_sold);
489-
total_usd_sold = total_usd_sold.saturating_add(usd_raised);
490-
total_ct_amount_left = total_ct_amount_left.saturating_sub(ct_sold);
491-
492-
bucket.current_price = bucket.current_price.saturating_sub(bucket.delta_price);
493-
}
494-
495-
total_usd_sold
496-
}
497459
}
498460

499461
pub mod typed_data_v4 {

0 commit comments

Comments
 (0)