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

👾 New Funding Runtime API #421

Merged
merged 1 commit into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions pallets/funding/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,16 +571,22 @@ sp_api::mock_impl_runtime_apis! {
}

impl ExtrinsicHelpers<Block, TestRuntime> for TestRuntime {
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 funding_asset_to_ct_amount_classic(project_id: ProjectId, funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> Balance {
PolimecFunding::funding_asset_to_ct_amount_classic(project_id, funding_asset, funding_asset_amount)
}

fn funding_asset_to_ct_amount_otm(project_id: ProjectId, funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> (Balance, Balance) {
PolimecFunding::funding_asset_to_ct_amount_otm(project_id, funding_asset, funding_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)
}

fn calculate_otm_fee(funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> Option<Balance> {
PolimecFunding::calculate_otm_fee(funding_asset, funding_asset_amount)
}

fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)> {
PolimecFunding::get_funding_asset_min_max_amounts(project_id, did, funding_asset, investor_type)
}
Expand Down
60 changes: 57 additions & 3 deletions pallets/funding/src/runtime_api.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use crate::traits::BondingRequirementCalculation;
#[allow(clippy::wildcard_imports)]
use crate::*;
use crate::{traits::BondingRequirementCalculation, HoldReason::Participation};
use alloc::collections::BTreeMap;
use frame_support::traits::fungibles::{Inspect, InspectEnumerable};
use itertools::Itertools;
use parity_scale_codec::{Decode, Encode};
use polimec_common::{credentials::InvestorType, ProvideAssetPrice, USD_DECIMALS};
use scale_info::TypeInfo;
use sp_arithmetic::Perquintill;
use sp_core::Get;
use sp_runtime::traits::Zero;

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)]
pub struct ProjectParticipationIds<T: Config> {
account: AccountIdOf<T>,
Expand Down Expand Up @@ -57,7 +60,10 @@ sp_api::decl_runtime_apis! {
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;
fn funding_asset_to_ct_amount_classic(project_id: ProjectId, funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> Balance;

/// Calculate how many CTs and what the OTM fee is for a given project and funding asset amount.
fn funding_asset_to_ct_amount_otm(project_id: ProjectId, funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> (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.
Expand All @@ -68,6 +74,7 @@ sp_api::decl_runtime_apis! {

/// Gets the minimum and maximum amount of FundingAsset a user can input in the UI.
fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)>;

}
}

Expand Down Expand Up @@ -138,7 +145,7 @@ impl<T: Config> Pallet<T> {
.collect_vec()
}

pub fn funding_asset_to_ct_amount(
pub fn funding_asset_to_ct_amount_classic(
project_id: ProjectId,
asset: AcceptedFundingAsset,
asset_amount: Balance,
Expand Down Expand Up @@ -176,6 +183,53 @@ impl<T: Config> Pallet<T> {
ct_amount
}

pub fn funding_asset_to_ct_amount_otm(
project_id: ProjectId,
funding_asset: AcceptedFundingAsset,
total_funding_asset_amount: Balance,
) -> (Balance, Balance) {
let project_details = ProjectsDetails::<T>::get(project_id).expect("Project not found");
let funding_asset_usd_price =
Pallet::<T>::get_decimals_aware_funding_asset_price(&funding_asset).expect("Price not found");
let otm_multiplier = ParticipationMode::OTM.multiplier();
let otm_fee_percentage = <T as pallet_proxy_bonding::Config>::FeePercentage::get() / otm_multiplier;

let divisor = FixedU128::from_perbill(otm_fee_percentage) + FixedU128::one();
let participating_funding_asset_amount =
divisor.reciprocal().unwrap().saturating_mul_int(total_funding_asset_amount);
let fee_funding_asset_amount = total_funding_asset_amount.saturating_sub(participating_funding_asset_amount);

let participating_usd_ticket_size =
funding_asset_usd_price.saturating_mul_int(participating_funding_asset_amount);

let mut ct_amount = Zero::zero();

// Contribution phase
if let Some(wap) = project_details.weighted_average_price {
ct_amount = wap.reciprocal().expect("Bad math").saturating_mul_int(participating_usd_ticket_size);
}
// Auction phase, we need to consider multiple buckets
else {
let mut usd_to_spend = participating_usd_ticket_size;
let mut current_bucket = Buckets::<T>::get(project_id).expect("Bucket not found");
while usd_to_spend > Zero::zero() {
let bucket_price = current_bucket.current_price;

let ct_to_buy = bucket_price.reciprocal().expect("Bad math").saturating_mul_int(usd_to_spend);
let ct_to_buy = ct_to_buy.min(current_bucket.amount_left);

ct_amount = ct_amount.saturating_add(ct_to_buy);
// if usd spent is 0, we will have an infinite loop
let usd_spent = bucket_price.saturating_mul_int(ct_to_buy).max(One::one());
usd_to_spend = usd_to_spend.saturating_sub(usd_spent);

current_bucket.update(ct_to_buy)
}
}

(ct_amount, fee_funding_asset_amount)
}

pub fn get_next_vesting_schedule_merge_candidates(
account_id: AccountIdOf<T>,
hold_reason: <T as Config>::RuntimeHoldReason,
Expand Down
4 changes: 2 additions & 2 deletions pallets/funding/src/tests/3_auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ mod bid_extrinsic {
inst.mint_funding_asset_to(vec![required_usdt.clone()]);

let ct_participation = inst.execute(|| {
<Pallet<TestRuntime>>::funding_asset_to_ct_amount(
<Pallet<TestRuntime>>::funding_asset_to_ct_amount_classic(
project_id,
AcceptedFundingAsset::USDT,
USDT_PARTICIPATION,
Expand Down Expand Up @@ -1057,7 +1057,7 @@ mod bid_extrinsic {
inst.mint_funding_asset_to(vec![required_usdt.clone()]);

let ct_participation = inst.execute(|| {
<Pallet<TestRuntime>>::funding_asset_to_ct_amount(
<Pallet<TestRuntime>>::funding_asset_to_ct_amount_classic(
project_id,
AcceptedFundingAsset::USDT,
USDT_PARTICIPATION,
Expand Down
4 changes: 2 additions & 2 deletions pallets/funding/src/tests/4_contribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ mod contribute_extrinsic {
inst.mint_funding_asset_to(vec![required_usdt.clone()]);

let ct_participation = inst.execute(|| {
<Pallet<TestRuntime>>::funding_asset_to_ct_amount(
<Pallet<TestRuntime>>::funding_asset_to_ct_amount_classic(
project_id,
AcceptedFundingAsset::USDT,
USDT_PARTICIPATION,
Expand Down Expand Up @@ -1221,7 +1221,7 @@ mod contribute_extrinsic {
inst.mint_funding_asset_to(vec![required_usdt.clone()]);

let ct_participation = inst.execute(|| {
<Pallet<TestRuntime>>::funding_asset_to_ct_amount(
<Pallet<TestRuntime>>::funding_asset_to_ct_amount_classic(
project_id,
AcceptedFundingAsset::USDT,
USDT_PARTICIPATION,
Expand Down
12 changes: 6 additions & 6 deletions pallets/funding/src/tests/runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ fn funding_asset_to_ct_amount() {
let expected_ct_amount_contribution = 9_315 * CT_UNIT;
inst.execute(|| {
let block_hash = System::block_hash(System::block_number());
let ct_amount = TestRuntime::funding_asset_to_ct_amount(
let ct_amount = TestRuntime::funding_asset_to_ct_amount_classic(
&TestRuntime,
block_hash,
project_id_1,
Expand Down Expand Up @@ -381,7 +381,7 @@ fn funding_asset_to_ct_amount() {
let expected_ct_amount_contribution = 5_714_720_000_000_000_000;
inst.execute(|| {
let block_hash = System::block_hash(System::block_number());
let ct_amount = TestRuntime::funding_asset_to_ct_amount(
let ct_amount = TestRuntime::funding_asset_to_ct_amount_classic(
&TestRuntime,
block_hash,
project_id_2,
Expand Down Expand Up @@ -433,7 +433,7 @@ fn funding_asset_to_ct_amount() {

inst.execute(|| {
let block_hash = System::block_hash(System::block_number());
let ct_amount = TestRuntime::funding_asset_to_ct_amount(
let ct_amount = TestRuntime::funding_asset_to_ct_amount_classic(
&TestRuntime,
block_hash,
project_id_3,
Expand All @@ -455,7 +455,7 @@ fn funding_asset_to_ct_amount() {

inst.execute(|| {
let block_hash = System::block_hash(System::block_number());
let ct_amount = TestRuntime::funding_asset_to_ct_amount(
let ct_amount = TestRuntime::funding_asset_to_ct_amount_classic(
&TestRuntime,
block_hash,
project_id_3,
Expand Down Expand Up @@ -570,7 +570,7 @@ fn calculate_otm_fee() {

let ct_amount = inst
.execute(|| {
TestRuntime::funding_asset_to_ct_amount(
TestRuntime::funding_asset_to_ct_amount_classic(
&TestRuntime,
block_hash,
project_id,
Expand Down Expand Up @@ -752,7 +752,7 @@ fn get_funding_asset_min_max_amounts() {
// This test requires the buyer to have contributed 4500 USD before calling the API
let required_ct = inst
.execute(|| {
TestRuntime::funding_asset_to_ct_amount(
TestRuntime::funding_asset_to_ct_amount_classic(
&TestRuntime,
block_hash,
project_id,
Expand Down
7 changes: 5 additions & 2 deletions runtimes/polimec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1534,8 +1534,11 @@ impl_runtime_apis! {
}

impl pallet_funding::runtime_api::ExtrinsicHelpers<Block, Runtime> for Runtime {
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 funding_asset_to_ct_amount_classic(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: Balance) -> Balance {
Funding::funding_asset_to_ct_amount_classic(project_id, asset, asset_amount)
}
fn funding_asset_to_ct_amount_otm(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: Balance) -> (Balance, Balance) {
Funding::funding_asset_to_ct_amount_otm(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)
Expand Down