Skip to content

Commit

Permalink
schedule merge runtime api
Browse files Browse the repository at this point in the history
  • Loading branch information
JuaniRios committed Sep 10, 2024
1 parent 43ac0a3 commit e3c6894
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 27 deletions.
2 changes: 1 addition & 1 deletion pallets/funding/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ pub fn default_bidder_multipliers() -> Vec<u8> {
vec![10u8, 3u8, 1u8, 7u8, 4u8]
}
pub fn default_community_contributor_multipliers() -> Vec<u8> {
vec![1u8, 1u8, 1u8, 1u8, 1u8]
vec![2u8, 1u8, 3u8, 1u8, 1u8]
}
pub fn default_remainder_contributor_multipliers() -> Vec<u8> {
vec![1u8, 11u8, 1u8, 1u8, 1u8]
Expand Down
45 changes: 27 additions & 18 deletions pallets/funding/src/functions/6_settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,17 +156,21 @@ impl<T: Config> Pallet<T> {
if funding_success && bid.status != BidStatus::Rejected {
let funding_end_block = project_details.funding_end_block.ok_or(Error::<T>::ImpossibleState)?;

let plmc_vesting_info =
let vesting_info =
Self::calculate_vesting_info(&bid.bidder, bid.multiplier, bid.plmc_bond.saturating_sub(refunded_plmc))
.map_err(|_| Error::<T>::BadMath)?;

VestingOf::<T>::add_release_schedule(
&bid.bidder,
plmc_vesting_info.total_amount,
plmc_vesting_info.amount_per_block,
funding_end_block,
HoldReason::Participation.into(),
)?;
if vesting_info.duration == 1u32.into() {
Self::release_participation_bond(&bid.bidder, vesting_info.total_amount)?;
} else {
VestingOf::<T>::add_release_schedule(
&bid.bidder,
vesting_info.total_amount,
vesting_info.amount_per_block,
funding_end_block,
HoldReason::Participation.into(),
)?;
}

Self::mint_contribution_tokens(project_id, &bid.bidder, final_ct_amount)?;

Expand All @@ -176,7 +180,7 @@ impl<T: Config> Pallet<T> {
bid.id,
ParticipationType::Bid,
final_ct_amount,
plmc_vesting_info.duration,
vesting_info.duration,
)?;

Self::release_funding_asset(
Expand Down Expand Up @@ -250,20 +254,25 @@ impl<T: Config> Pallet<T> {
)?;
} else {
// Calculate the vesting info and add the release schedule
let vest_info = Self::calculate_vesting_info(
let vesting_info = Self::calculate_vesting_info(
&contribution.contributor,
contribution.multiplier,
contribution.plmc_bond,
)
.map_err(|_| Error::<T>::BadMath)?;

VestingOf::<T>::add_release_schedule(
&contribution.contributor,
vest_info.total_amount,
vest_info.amount_per_block,
funding_end_block,
HoldReason::Participation.into(),
)?;
if vesting_info.duration == 1u32.into() {
Self::release_participation_bond(&contribution.contributor, vesting_info.total_amount)?;
} else {
VestingOf::<T>::add_release_schedule(
&contribution.contributor,
vesting_info.total_amount,
vesting_info.amount_per_block,
funding_end_block,
HoldReason::Participation.into(),
)?;
}

// Mint the contribution tokens
Self::mint_contribution_tokens(project_id, &contribution.contributor, contribution.ct_amount)?;

Expand All @@ -282,7 +291,7 @@ impl<T: Config> Pallet<T> {
contribution.id,
ParticipationType::Contribution,
contribution.ct_amount,
vest_info.duration,
vesting_info.duration,
)?;

final_ct_amount = contribution.ct_amount;
Expand Down
1 change: 0 additions & 1 deletion pallets/funding/src/instantiator/calculations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ impl<
output.merge_accounts(MergeOperation::Add)
}

// WARNING: Only put bids that you are sure will be done before the random end of the closing auction
pub fn calculate_auction_plmc_returned_from_all_bids_made(
&mut self,
// bids in the order they were made
Expand Down
3 changes: 2 additions & 1 deletion pallets/funding/src/instantiator/chain_interactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,8 @@ impl<
ProjectStatus::CommunityRound(..) =>
for cont in contributions {
let did = generate_did_from_account(cont.contributor.clone());
let investor_type = InvestorType::Retail;
// We use institutional to be able to test most multipliers.
let investor_type = InvestorType::Institutional;
let params = DoContributeParams::<T> {
contributor: cont.contributor,
project_id,
Expand Down
3 changes: 2 additions & 1 deletion pallets/funding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub type ContributionInfoOf<T> =
pub type BucketOf<T> = Bucket<PriceOf<T>>;
pub type WeightInfoOf<T> = <T as Config>::WeightInfo;
pub type VestingOf<T> = pallet_linear_release::Pallet<T>;
pub type BlockNumberToBalanceOf<T> = <T as pallet_linear_release::Config>::BlockNumberToBalance;

pub const PLMC_FOREIGN_ID: u32 = 3344;
pub const PLMC_DECIMALS: u8 = 10;
Expand Down Expand Up @@ -332,7 +333,7 @@ pub mod pallet {
+ Member;

/// The hold reason enum constructed by the construct_runtime macro
type RuntimeHoldReason: From<HoldReason>;
type RuntimeHoldReason: From<HoldReason> + Parameter + MaxEncodedLen + Copy;

/// The origin enum constructed by the construct_runtime macro
type RuntimeOrigin: IsType<<Self as frame_system::Config>::RuntimeOrigin>
Expand Down
5 changes: 5 additions & 0 deletions pallets/funding/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,5 +552,10 @@ sp_api::mock_impl_runtime_apis! {
fn funding_asset_to_ct_amount(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: Balance) -> Balance {
PolimecFunding::funding_asset_to_ct_amount(project_id, asset, asset_amount)
}
fn get_next_vesting_schedule_merge_candidates(account: AccountId, hold_reason: RuntimeHoldReason, end_max_delta: Balance) -> Option<(u32, u32)> {
PolimecFunding::get_next_vesting_schedule_merge_candidates(account, hold_reason, end_max_delta)
}


}
}
41 changes: 40 additions & 1 deletion pallets/funding/src/runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,15 @@ sp_api::decl_runtime_apis! {
fn projects_by_did(did: Did) -> Vec<ProjectId>;
}

#[api_version(1)]
#[api_version(2)]
pub trait ExtrinsicHelpers<T: Config> {
/// Get the current price of a contribution token (either current bucket in the auction, or WAP in contribution phase),
/// and calculate the amount of tokens that can be bought with the given amount USDT/USDC/DOT.
fn funding_asset_to_ct_amount(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: Balance) -> Balance;

/// Get the indexes of vesting schedules that are good candidates to be merged.
/// Schedules that have not yet started are de-facto bad candidates.
fn get_next_vesting_schedule_merge_candidates(account_id: AccountIdOf<T>, hold_reason: <T as Config>::RuntimeHoldReason, end_max_delta: Balance) -> Option<(u32, u32)>;
}
}

Expand Down Expand Up @@ -169,6 +173,41 @@ impl<T: Config> Pallet<T> {
ct_amount
}

pub fn get_next_vesting_schedule_merge_candidates(
account_id: AccountIdOf<T>,
hold_reason: <T as Config>::RuntimeHoldReason,
end_max_delta: Balance,
) -> Option<(u32, u32)> {
let schedules = pallet_linear_release::Vesting::<T>::get(account_id, hold_reason)?
.into_iter()
.enumerate()
// Filter out schedules with future starting blocks before collecting them into a vector.
.filter_map(|(i, schedule)| {
if schedule.starting_block > <frame_system::Pallet<T>>::block_number() {
None
} else {
Some((i, schedule.ending_block_as_balance::<BlockNumberToBalanceOf<T>>()))
}
})
.collect::<Vec<_>>();

let mut inspected_schedules = BTreeMap::new();

for (i, schedule_end) in schedules {
let range_start = schedule_end.saturating_sub(end_max_delta);
let range_end = schedule_end.saturating_add(end_max_delta);

// All entries where the ending_block is between range_start and range_end.
if let Some((_, &j)) = inspected_schedules.range(range_start..=range_end).next() {
return Some((j as u32, i as u32));
}

inspected_schedules.insert(schedule_end, i);
}

None
}

pub fn all_project_participations_by_did(project_id: ProjectId, did: Did) -> Vec<ProjectParticipationIds<T>> {
let evaluations = Evaluations::<T>::iter_prefix((project_id,))
.filter(|((_account_id, _evaluation_id), evaluation)| evaluation.did == did)
Expand Down
8 changes: 4 additions & 4 deletions pallets/funding/src/tests/6_settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ mod settle_bid_extrinsic {
let auction_allocation =
project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size;
let partial_amount_bid_params =
BidParams::new(BIDDER_1, auction_allocation, 1u8, AcceptedFundingAsset::USDT);
BidParams::new(BIDDER_1, auction_allocation, 3u8, AcceptedFundingAsset::USDT);
let lower_price_bid_params = BidParams::new(BIDDER_2, 2000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT);
let bids = vec![partial_amount_bid_params.clone(), lower_price_bid_params.clone()];

Expand Down Expand Up @@ -461,10 +461,10 @@ mod settle_bid_extrinsic {
true,
);

// Multiplier one should be fully unbonded the next block
inst.advance_time(1_u64);

let hold_reason: RuntimeHoldReason = HoldReason::Participation.into();
let vesting_time = Multiplier::force_new(3).calculate_vesting_duration::<TestRuntime>();
let now = inst.current_block();
inst.jump_to_block(now + vesting_time + 1u64);
inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BIDDER_1), hold_reason).expect("Vesting failed"));

inst.assert_plmc_free_balance(BIDDER_1, expected_plmc_refund + expected_final_plmc_bonded + ed);
Expand Down
75 changes: 75 additions & 0 deletions pallets/funding/src/tests/runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,81 @@ fn funding_asset_to_ct_amount() {
});
}

#[test]
fn get_next_vesting_schedule_merge_candidates() {
let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext())));
let evaluations = vec![
UserToUSDBalance::new(EVALUATOR_1, 500_000 * USD_UNIT),
UserToUSDBalance::new(EVALUATOR_2, 250_000 * USD_UNIT),
UserToUSDBalance::new(BIDDER_1, 320_000 * USD_UNIT),
];
let bids = vec![
BidParams::new(BIDDER_1, 50_000 * CT_UNIT, 10u8, AcceptedFundingAsset::USDT),
BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT),
BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT),
];
let remaining_contributions = vec![
ContributionParams::new(BIDDER_1, 1_000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT),
ContributionParams::new(BIDDER_1, 15_000 * CT_UNIT, 10u8, AcceptedFundingAsset::USDT),
ContributionParams::new(BIDDER_1, 100 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT),
];

let project_id = inst.create_finished_project(
default_project_metadata(ISSUER_1),
ISSUER_1,
None,
evaluations.clone(),
bids.clone(),
default_community_contributions(),
remaining_contributions.clone(),
);
assert_eq!(ProjectStatus::SettlementStarted(FundingOutcome::Success), inst.go_to_next_state(project_id));
inst.execute(|| {
PolimecFunding::settle_evaluation(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 2).unwrap();
PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0).unwrap();
PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 1).unwrap();
PolimecFunding::settle_contribution(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 5).unwrap();
PolimecFunding::settle_contribution(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 6).unwrap();
PolimecFunding::settle_contribution(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 7).unwrap();
});

let hold_reason: mock::RuntimeHoldReason = HoldReason::Participation.into();
let bidder_1_schedules =
inst.execute(|| pallet_linear_release::Vesting::<TestRuntime>::get(BIDDER_1, hold_reason).unwrap().to_vec());
// Evaluations didn't get a vesting schedule
assert_eq!(bidder_1_schedules.len(), 4);

inst.execute(|| {
let block_hash = System::block_hash(System::block_number());
let (idx_1, idx_2) = TestRuntime::get_next_vesting_schedule_merge_candidates(
&TestRuntime,
block_hash,
BIDDER_1,
HoldReason::Participation.into(),
// within 100 blocks
100u128,
)
.unwrap()
.unwrap();
assert_eq!((idx_1, idx_2), (1, 2));

// Merging the two schedules deletes them and creates a new one at the end of the vec.
LinearRelease::merge_schedules(RuntimeOrigin::signed(BIDDER_1), idx_1, idx_2, hold_reason).unwrap();

let (idx_1, idx_2) = TestRuntime::get_next_vesting_schedule_merge_candidates(
&TestRuntime,
block_hash,
BIDDER_1,
HoldReason::Participation.into(),
// within 100 blocks
100u128,
)
.unwrap()
.unwrap();
assert_eq!((idx_1, idx_2), (0, 1));
});
}

#[test]
fn all_project_participations_by_did() {
let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext())));
Expand Down
3 changes: 3 additions & 0 deletions runtimes/polimec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,9 @@ impl_runtime_apis! {
fn funding_asset_to_ct_amount(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: Balance) -> Balance {
Funding::funding_asset_to_ct_amount(project_id, asset, asset_amount)
}
fn get_next_vesting_schedule_merge_candidates(account: AccountId, hold_reason: RuntimeHoldReason, end_max_delta: Balance) -> Option<(u32, u32)> {
Funding::get_next_vesting_schedule_merge_candidates(account, hold_reason, end_max_delta)
}
}

#[cfg(feature = "try-runtime")]
Expand Down

0 comments on commit e3c6894

Please sign in to comment.