polimec_common/
lib.rs

1// Polimec Blockchain – https://www.polimec.org/
2// Copyright (C) Polimec 2022. All rights reserved.
3
4// The Polimec Blockchain is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Polimec Blockchain is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17#![cfg_attr(not(feature = "std"), no_std)]
18
19use frame_support::{pallet_prelude::*, traits::tokens::fungible};
20use sp_runtime::{
21	traits::{CheckedDiv, CheckedMul},
22	FixedPointNumber, RuntimeDebug,
23};
24use sp_std::prelude::*;
25pub use xcm::v4::{opaque::Xcm, Assets, Location, QueryId, SendError, SendResult, SendXcm, XcmHash};
26
27pub mod credentials;
28
29/// A release schedule over a fungible. This allows a particular fungible to have release limits
30/// applied to it.
31pub trait ReleaseSchedule<AccountId, Reason> {
32	/// The quantity used to denote time; usually just a `BlockNumber`.
33	type Moment;
34
35	/// The currency that this schedule applies to.
36	type Currency: fungible::InspectHold<AccountId>
37		+ fungible::MutateHold<AccountId>
38		+ fungible::BalancedHold<AccountId>;
39
40	/// Get the amount that is possible to vest (i.e release) at the current block
41	fn vesting_balance(
42		who: &AccountId,
43		reason: Reason,
44	) -> Option<<Self::Currency as fungible::Inspect<AccountId>>::Balance>;
45
46	/// Get the amount that was scheduled, regardless if it was already vested or not
47	fn total_scheduled_amount(
48		who: &AccountId,
49		reason: Reason,
50	) -> Option<<Self::Currency as fungible::Inspect<AccountId>>::Balance>;
51
52	/// Release the vested amount of the given account.
53	fn vest(
54		who: AccountId,
55		reason: Reason,
56	) -> Result<<Self::Currency as fungible::Inspect<AccountId>>::Balance, DispatchError>;
57
58	/// Adds a release schedule to a given account.
59	///
60	/// If the account has `MaxVestingSchedules`, an Error is returned and nothing
61	/// is updated.
62	///
63	/// Is a no-op if the amount to be vested is zero.
64	///
65	/// NOTE: This doesn't alter the free balance of the account.
66	fn add_release_schedule(
67		who: &AccountId,
68		locked: <Self::Currency as fungible::Inspect<AccountId>>::Balance,
69		per_block: <Self::Currency as fungible::Inspect<AccountId>>::Balance,
70		starting_block: Self::Moment,
71		reason: Reason,
72	) -> DispatchResult;
73
74	/// Set a release schedule to a given account, without locking any funds.
75	///
76	/// If the account has `MaxVestingSchedules`, an Error is returned and nothing
77	/// is updated.
78	///
79	/// Is a no-op if the amount to be vested is zero.
80	///
81	/// NOTE: This doesn't alter the free balance of the account.
82	fn set_release_schedule(
83		who: &AccountId,
84		locked: <Self::Currency as fungible::Inspect<AccountId>>::Balance,
85		per_block: <Self::Currency as fungible::Inspect<AccountId>>::Balance,
86		starting_block: Self::Moment,
87		reason: Reason,
88	) -> DispatchResult;
89
90	/// Checks if `add_release_schedule` would work against `who`.
91	fn can_add_release_schedule(
92		who: &AccountId,
93		locked: <Self::Currency as fungible::Inspect<AccountId>>::Balance,
94		per_block: <Self::Currency as fungible::Inspect<AccountId>>::Balance,
95		starting_block: Self::Moment,
96		reason: Reason,
97	) -> DispatchResult;
98
99	/// Remove a release schedule for a given account.
100	///
101	/// NOTE: This doesn't alter the free balance of the account.
102	fn remove_vesting_schedule(who: &AccountId, schedule_index: u32, reason: Reason) -> DispatchResult;
103
104	fn remove_all_vesting_schedules(who: &AccountId, reason: Reason) -> DispatchResult;
105}
106
107pub mod migration_types {
108	#[allow(clippy::wildcard_imports)]
109	use super::*;
110
111	#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
112	pub struct MigrationOrigin {
113		pub user: Location,
114		pub id: u32,
115		pub participation_type: ParticipationType,
116	}
117	impl PartialOrd for MigrationOrigin {
118		fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
119			Some(self.cmp(other))
120		}
121	}
122	impl Ord for MigrationOrigin {
123		fn cmp(&self, other: &Self) -> core::cmp::Ordering {
124			if self.participation_type == other.participation_type {
125				self.id.cmp(&other.id)
126			} else {
127				self.participation_type.cmp(&other.participation_type)
128			}
129		}
130	}
131
132	#[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)]
133	pub enum ParticipationType {
134		Evaluation,
135		Bid,
136		Contribution,
137	}
138
139	#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
140	pub struct MigrationInfo {
141		pub contribution_token_amount: u128,
142		pub vesting_time: u64,
143	}
144	impl From<(u128, u64)> for MigrationInfo {
145		fn from((contribution_token_amount, vesting_time): (u128, u64)) -> Self {
146			Self { contribution_token_amount, vesting_time }
147		}
148	}
149
150	#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)]
151	pub enum MigrationStatus {
152		NotStarted,
153		Sent(QueryId),
154		Confirmed,
155		Failed,
156	}
157
158	#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
159	pub struct Migration {
160		pub origin: MigrationOrigin,
161		pub info: MigrationInfo,
162	}
163
164	impl Migration {
165		pub fn new(origin: MigrationOrigin, info: MigrationInfo) -> Self {
166			Self { origin, info }
167		}
168	}
169
170	#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, Default)]
171	pub struct Migrations(Vec<Migration>);
172	impl FromIterator<Migration> for Migrations {
173		fn from_iter<T: IntoIterator<Item = Migration>>(iter: T) -> Self {
174			Migrations::from(iter.into_iter().collect::<Vec<_>>())
175		}
176	}
177
178	impl Migrations {
179		pub fn new() -> Self {
180			Self(Vec::new())
181		}
182
183		pub fn inner(self) -> Vec<Migration> {
184			self.0
185		}
186
187		pub fn push(&mut self, migration: Migration) {
188			self.0.push(migration)
189		}
190
191		pub fn from(migrations: Vec<Migration>) -> Self {
192			Self(migrations)
193		}
194
195		pub fn contains(&self, migration: &Migration) -> bool {
196			self.0.contains(migration)
197		}
198
199		pub fn len(&self) -> usize {
200			self.0.len()
201		}
202
203		pub fn is_empty(&self) -> bool {
204			self.0.is_empty()
205		}
206
207		pub fn origins(&self) -> Vec<MigrationOrigin> {
208			self.0.iter().map(|migration| migration.origin.clone()).collect()
209		}
210
211		pub fn infos(&self) -> Vec<MigrationInfo> {
212			self.0.iter().map(|migration| migration.info.clone()).collect()
213		}
214
215		pub fn total_ct_amount(&self) -> u128 {
216			self.0.iter().map(|migration| migration.info.contribution_token_amount).sum()
217		}
218
219		pub fn biggest_vesting_time(&self) -> u64 {
220			self.0.iter().map(|migration| migration.info.vesting_time).max().unwrap_or(0)
221		}
222	}
223}
224
225pub const USD_DECIMALS: u8 = 6;
226pub const USD_UNIT: u128 = 10u128.pow(USD_DECIMALS as u32);
227pub const PLMC_DECIMALS: u8 = 10;
228pub const PLMC_FOREIGN_ID: u32 = 3344;
229
230pub trait ProvideAssetPrice {
231	type AssetId;
232	type Price: FixedPointNumber;
233	/// Gets the price of an asset.
234	///
235	/// Returns `None` if the price is not available.
236	fn get_price(asset_id: Self::AssetId) -> Option<Self::Price>;
237
238	/// Prices define the relationship between USD/Asset. When to and from that asset, we need to be aware that they might
239	/// have different decimals. This function calculates the relationship having in mind the decimals. For example:
240	/// if the price is 2.5, our underlying USD unit has 6 decimals, and the asset has 8 decimals, the price will be
241	/// calculated like so: `(2.5USD * 10^6) / (1 * 10^8) = 0.025`. And so if we want to convert 20 of the asset to USD,
242	/// we would do `0.025(USD/Asset)FixedPointNumber * 20_000_000_00(Asset)u128 = 50_000_000` which is 50 USD with 6 decimals
243	fn calculate_decimals_aware_price(
244		original_price: Self::Price,
245		usd_decimals: u8,
246		asset_decimals: u8,
247	) -> Option<Self::Price> {
248		let usd_unit = 10u128.checked_pow(usd_decimals.into())?;
249		let usd_price_with_decimals = original_price.checked_mul_int(usd_unit)?;
250		let asset_unit = 10u128.checked_pow(asset_decimals.into())?;
251
252		Self::Price::checked_from_rational(usd_price_with_decimals, asset_unit)
253	}
254
255	fn convert_back_to_normal_price(
256		decimals_aware_price: Self::Price,
257		usd_decimals: u8,
258		asset_decimals: u8,
259	) -> Option<Self::Price> {
260		let abs_diff: u32 = asset_decimals.abs_diff(usd_decimals).into();
261		let abs_diff_unit = 10u128.checked_pow(abs_diff)?;
262		// We are pretty sure this is going to be representable because the number size is not the size of the asset decimals, but the difference between the asset and usd decimals
263		let abs_diff_fixed = Self::Price::checked_from_rational(abs_diff_unit, 1)?;
264		if usd_decimals > asset_decimals {
265			decimals_aware_price.checked_div(&abs_diff_fixed)
266		} else {
267			decimals_aware_price.checked_mul(&abs_diff_fixed)
268		}
269	}
270
271	fn get_decimals_aware_price(asset_id: Self::AssetId, usd_decimals: u8, asset_decimals: u8) -> Option<Self::Price> {
272		let original_price = Self::get_price(asset_id)?;
273		Self::calculate_decimals_aware_price(original_price, usd_decimals, asset_decimals)
274	}
275}