diff --git a/Cargo.lock b/Cargo.lock index d4d553822..a31d51c60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,6 +446,7 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", + "hex-literal", "kusama-runtime-constants", "log", "pallet-asset-conversion", @@ -10527,6 +10528,23 @@ dependencies = [ "prost 0.12.6", ] +[[package]] +name = "proxy-bonding" +version = "0.8.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-linear-release", + "parity-scale-codec", + "polimec-common", + "scale-info", + "sp-io", + "sp-runtime", +] + [[package]] name = "psm" version = "0.1.21" diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 69a8d18b7..91a3f5f38 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -140,5 +140,42 @@ std = [ "xcm/std", ] development-settings = [ "polimec-runtime/development-settings" ] -runtime-benchmarks = [] +runtime-benchmarks = [ + "asset-hub-polkadot-runtime/runtime-benchmarks", + "polkadot-runtime/runtime-benchmarks", + "penpal-runtime/runtime-benchmarks", + "pallet-democracy/runtime-benchmarks", + "pallet-dispenser/runtime-benchmarks", + "pallet-elections-phragmen/runtime-benchmarks", + "pallet-funding/runtime-benchmarks", + "pallet-linear-release/runtime-benchmarks", + "pallet-parachain-staking/runtime-benchmarks", + "polimec-receiver/runtime-benchmarks", + "polimec-common/runtime-benchmarks", + "polimec-common-test-utils/runtime-benchmarks", + "polimec-runtime/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "orml-oracle/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-collective/runtime-benchmarks", + "pallet-im-online/runtime-benchmarks", + "pallet-membership/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", + "pallet-treasury/runtime-benchmarks", + "pallet-vesting/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", + "polkadot-runtime-parachains/runtime-benchmarks", + "polkadot-service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks" +] diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 145ab87b9..4ad065a3f 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -19,10 +19,7 @@ //! Benchmarking setup for Funding pallet use super::*; -use crate::{ - instantiator::*, - traits::{ProvideAssetPrice, SetPrices}, -}; +use crate::{instantiator::*, traits::SetPrices}; use frame_benchmarking::v2::*; use frame_support::{ assert_ok, @@ -35,7 +32,7 @@ use frame_support::{ }; use itertools::Itertools; use parity_scale_codec::{Decode, Encode}; -use polimec_common::{credentials::InvestorType, ReleaseSchedule, USD_DECIMALS, USD_UNIT}; +use polimec_common::{credentials::InvestorType, ProvideAssetPrice, ReleaseSchedule, USD_DECIMALS, USD_UNIT}; use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; use sp_arithmetic::Percent; use sp_core::H256; diff --git a/pallets/funding/src/functions/2_evaluation.rs b/pallets/funding/src/functions/2_evaluation.rs index bf135cc62..7a92ff1ab 100644 --- a/pallets/funding/src/functions/2_evaluation.rs +++ b/pallets/funding/src/functions/2_evaluation.rs @@ -1,6 +1,6 @@ #[allow(clippy::wildcard_imports)] use super::*; - +use polimec_common::ProvideAssetPrice; impl Pallet { /// Called by user extrinsic /// Starts the evaluation round of a project. It needs to be called by the project issuer. diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 1ce4d0c96..0ce7511d3 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -1,7 +1,7 @@ -use sp_runtime::traits::CheckedAdd; - #[allow(clippy::wildcard_imports)] use super::*; +use polimec_common::ProvideAssetPrice; +use sp_runtime::traits::CheckedAdd; // Helper functions // ATTENTION: if this is called directly, it will not be transactional diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index 2e715dc91..9e25a4cb7 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -2,8 +2,7 @@ use super::*; use core::cmp::Ordering; use itertools::GroupBy; -use polimec_common::USD_DECIMALS; - +use polimec_common::{ProvideAssetPrice, USD_DECIMALS}; impl< T: Config + pallet_balances::Config>, AllPalletsWithoutSystem: OnFinalize> + OnIdle> + OnInitialize>, diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs index 3e732a77e..91bd80edd 100644 --- a/pallets/funding/src/instantiator/tests.rs +++ b/pallets/funding/src/instantiator/tests.rs @@ -5,12 +5,11 @@ use crate::{ defaults::{bounded_name, bounded_symbol, default_evaluations, default_project_metadata, ipfs_hash}, CT_DECIMALS, CT_UNIT, }, - traits::ProvideAssetPrice, *, }; use core::cell::RefCell; use itertools::Itertools; -use polimec_common::{USD_DECIMALS, USD_UNIT}; +use polimec_common::{ProvideAssetPrice, USD_DECIMALS, USD_UNIT}; use sp_arithmetic::Percent; #[test] diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index bf9fe2137..6ada800ae 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -197,7 +197,7 @@ pub const PLMC_DECIMALS: u8 = 10; pub mod pallet { #[allow(clippy::wildcard_imports)] use super::*; - use crate::traits::{BondingRequirementCalculation, ProvideAssetPrice, VestingDurationCalculation}; + use crate::traits::{BondingRequirementCalculation, VestingDurationCalculation}; use core::ops::RangeInclusive; use frame_support::{ pallet_prelude::*, @@ -205,6 +205,7 @@ pub mod pallet { traits::{OnFinalize, OnIdle, OnInitialize}, }; use frame_system::pallet_prelude::*; + use polimec_common::ProvideAssetPrice; use sp_arithmetic::Percent; use sp_runtime::{ traits::{Convert, ConvertBack, Get}, diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 559d75e88..3707b237e 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -20,9 +20,8 @@ use super::*; use crate as pallet_funding; -use crate::{ - runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, ProjectParticipationIds, UserInformation}, - traits::ProvideAssetPrice, +use crate::runtime_api::{ + ExtrinsicHelpers, Leaderboards, ProjectInformation, ProjectParticipationIds, UserInformation, }; use core::ops::RangeInclusive; use frame_support::{ @@ -34,7 +33,7 @@ use frame_support::{ }; use frame_system as system; use frame_system::{EnsureRoot, RawOrigin as SystemRawOrigin}; -use polimec_common::{credentials::EnsureInvestor, DummyXcmSender, USD_UNIT}; +use polimec_common::{credentials::EnsureInvestor, DummyXcmSender, ProvideAssetPrice, USD_UNIT}; use polkadot_parachain_primitives::primitives::Sibling; use sp_arithmetic::Percent; use sp_core::H256; diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index 7c87835aa..8a3e27c05 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -4,10 +4,9 @@ use alloc::collections::BTreeMap; use frame_support::traits::fungibles::{metadata::Inspect as MetadataInspect, Inspect, InspectEnumerable}; use itertools::Itertools; use parity_scale_codec::{Decode, Encode}; -use polimec_common::USD_DECIMALS; +use polimec_common::{ProvideAssetPrice, USD_DECIMALS}; use scale_info::TypeInfo; use sp_runtime::traits::Zero; - #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] pub struct ProjectParticipationIds { account: AccountIdOf, diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index 837d9dca5..acb4f182f 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -1,9 +1,6 @@ use super::*; use crate::{ - instantiator::*, - mock::*, - traits::{ProvideAssetPrice, VestingDurationCalculation}, - CurrencyMetadata, Error, ProjectMetadata, TicketSize, + instantiator::*, mock::*, traits::VestingDurationCalculation, CurrencyMetadata, Error, ProjectMetadata, TicketSize, }; use defaults::*; use frame_support::{ @@ -15,7 +12,7 @@ use frame_support::{ }; use itertools::Itertools; use parachains_common::DAYS; -use polimec_common::{ReleaseSchedule, USD_DECIMALS, USD_UNIT}; +use polimec_common::{ProvideAssetPrice, ReleaseSchedule, USD_DECIMALS, USD_UNIT}; use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; use sp_arithmetic::{traits::Zero, Percent, Perquintill}; use sp_runtime::TokenError; diff --git a/pallets/funding/src/traits.rs b/pallets/funding/src/traits.rs index 94af27f2d..4ef8642be 100644 --- a/pallets/funding/src/traits.rs +++ b/pallets/funding/src/traits.rs @@ -31,50 +31,6 @@ pub trait VestingDurationCalculation { fn calculate_vesting_duration(&self) -> BlockNumberFor; } -pub trait ProvideAssetPrice { - type AssetId; - type Price: FixedPointNumber; - fn get_price(asset_id: Self::AssetId) -> Option; - - /// Prices define the relationship between USD/Asset. When to and from that asset, we need to be aware that they might - /// have different decimals. This function calculates the relationship having in mind the decimals. For example: - /// if the price is 2.5, our underlying USD unit has 6 decimals, and the asset has 8 decimals, the price will be - /// 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, - /// we would do `0.025(USD/Asset)FixedPointNumber * 20_000_000_00(Asset)u128 = 50_000_000` which is 50 USD with 6 decimals - fn calculate_decimals_aware_price( - original_price: Self::Price, - usd_decimals: u8, - asset_decimals: u8, - ) -> Option { - let usd_unit = 10u128.checked_pow(usd_decimals.into())?; - let usd_price_with_decimals = original_price.checked_mul_int(usd_unit)?; - let asset_unit = 10u128.checked_pow(asset_decimals.into())?; - - Self::Price::checked_from_rational(usd_price_with_decimals, asset_unit) - } - - fn convert_back_to_normal_price( - decimals_aware_price: Self::Price, - usd_decimals: u8, - asset_decimals: u8, - ) -> Option { - let abs_diff: u32 = asset_decimals.abs_diff(usd_decimals).into(); - let abs_diff_unit = 10u128.checked_pow(abs_diff)?; - // 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 - let abs_diff_fixed = Self::Price::checked_from_rational(abs_diff_unit, 1)?; - if usd_decimals > asset_decimals { - decimals_aware_price.checked_div(&abs_diff_fixed) - } else { - decimals_aware_price.checked_mul(&abs_diff_fixed) - } - } - - fn get_decimals_aware_price(asset_id: Self::AssetId, usd_decimals: u8, asset_decimals: u8) -> Option { - let original_price = Self::get_price(asset_id)?; - Self::calculate_decimals_aware_price(original_price, usd_decimals, asset_decimals) - } -} - pub trait DoRemainingOperation { fn has_remaining_operations(&self) -> bool; diff --git a/pallets/proxy-bonding/Cargo.toml b/pallets/proxy-bonding/Cargo.toml new file mode 100644 index 000000000..35fadd2e0 --- /dev/null +++ b/pallets/proxy-bonding/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "proxy-bonding" +authors.workspace = true +documentation.workspace = true +edition.workspace = true +homepage.workspace = true +license-file.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +frame-system.workspace = true +frame-support.workspace = true +frame-benchmarking = { workspace = true, optional = true } +sp-runtime.workspace = true +polimec-common.workspace = true +parity-scale-codec.workspace = true +scale-info.workspace = true + +[dev-dependencies] +sp-io.workspace = true +pallet-linear-release.workspace = true +pallet-balances.workspace = true +pallet-assets.workspace = true + + +[features] +default = ["std"] + +std = [ + "frame-system/std", + "frame-support/std", + "sp-runtime/std", + "polimec-common/std", + "parity-scale-codec/std", + "scale-info/std", + "frame-benchmarking?/std", + + "sp-io/std", + "pallet-linear-release/std", + "pallet-balances/std", + "pallet-assets/std" +] + +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "polimec-common/try-runtime" +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "polimec-common/runtime-benchmarks" +] diff --git a/pallets/proxy-bonding/src/functions.rs b/pallets/proxy-bonding/src/functions.rs new file mode 100644 index 000000000..9ab5104d4 --- /dev/null +++ b/pallets/proxy-bonding/src/functions.rs @@ -0,0 +1,69 @@ +use crate::{AccountIdOf, AssetId, BalanceOf, Config, HoldReasonOf, Pallet}; +use frame_support::traits::{ + fungible, + fungible::{Inspect, Mutate, MutateHold}, + fungibles, + fungibles::Mutate as FungiblesMutate, + tokens::{Fortitude, Precision, Preservation}, +}; +use polimec_common::ProvideAssetPrice; +use sp_runtime::{ + traits::{AccountIdConversion, CheckedMul, Get}, + FixedPointNumber, +}; + +impl Pallet { + pub fn bond_on_behalf_of( + account: T::AccountId, + bond_amount: BalanceOf, + fee_asset: AssetId, + derivation_path: u32, + hold_reason: HoldReasonOf, + ) -> Result, &'static str> { + let treasury = T::Treasury::get(); + let bonding_account: AccountIdOf = T::RootId::get().into_sub_account_truncating(derivation_path); + let existential_deposit = >::minimum_balance(); + + let bonding_token_price = T::PriceProvider::get_decimals_aware_price( + T::BondingTokenId::get(), + T::UsdDecimals::get(), + T::BondingTokenDecimals::get(), + ) + .ok_or("Price not available")?; + + let fee_asset_decimals = >>::decimals(fee_asset); + let fee_token_price = + T::PriceProvider::get_decimals_aware_price(fee_asset, T::UsdDecimals::get(), fee_asset_decimals) + .ok_or("Price not available")?; + + let bonded_in_usd = bonding_token_price.saturating_mul_int(bond_amount); + let fee_in_usd = T::FeePercentage::get() * bonded_in_usd; + let fee_in_fee_asset = + fee_token_price.reciprocal().ok_or("Price not available")?.saturating_mul_int(fee_in_usd); + + // Pay the fee from the user to the treasury + T::FeeToken::transfer(fee_asset, &account, &treasury, fee_in_fee_asset, Preservation::Preserve)?; + + // Ensure the sub-account has an ED by the treasury. This will be refunded after all the tokens are unlocked + if T::BondingToken::balance(&bonding_account) < existential_deposit { + T::BondingToken::transfer( + &treasury.clone(), + &bonding_account, + existential_deposit, + Preservation::Preserve, + )?; + } + // Bond the PLMC on behalf of the user + T::BondingToken::transfer_and_hold( + &hold_reason, + &treasury.clone(), + &bonding_account.clone(), + bond_amount, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + )?; + + Ok(bonding_account) + } +} diff --git a/pallets/proxy-bonding/src/lib.rs b/pallets/proxy-bonding/src/lib.rs new file mode 100644 index 000000000..a887a0670 --- /dev/null +++ b/pallets/proxy-bonding/src/lib.rs @@ -0,0 +1,129 @@ +// Polimec Blockchain – https://www.polimec.org/ +// Copyright (C) Polimec 2022. All rights reserved. + +// The Polimec Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Polimec Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// Needed due to empty sections raising the warning +#![allow(unreachable_patterns)] +pub use pallet::*; + +mod functions; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ + pallet_prelude::{Weight, *}, + traits::{ + fungible, + fungible::{Inspect, InspectHold, Mutate}, + fungibles, + tokens::Preservation, + }, + }; + use frame_system::pallet_prelude::*; + use polimec_common::ProvideAssetPrice; + use sp_runtime::{ + traits::{AccountIdConversion, CheckedAdd, One, Zero}, + Perbill, TypeId, + }; + pub type AssetId = u32; + pub type BalanceOf = <::BondingToken as fungible::Inspect>>::Balance; + pub type AccountIdOf = ::AccountId; + pub type HoldReasonOf = <::BondingToken as fungible::InspectHold>>::Reason; + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The pallet giving access to the bonding token + type BondingToken: fungible::Inspect + + fungible::Mutate + + fungible::MutateHold; + + type BondingTokenDecimals: Get; + type UsdDecimals: Get; + type BondingTokenId: Get; + + /// The pallet giving access to fee-paying assets, like USDT + type FeeToken: fungibles::Inspect, AssetId = AssetId> + + fungibles::Mutate, AssetId = AssetId> + + fungibles::InspectHold, AssetId = AssetId> + + fungibles::metadata::Inspect, AssetId = AssetId>; + + type FeePercentage: Get; + + /// Method to get the price of an asset like USDT or PLMC. Likely to come from an oracle + type PriceProvider: ProvideAssetPrice; + + /// The account holding the tokens to be bonded. Normally the treasury + type Treasury: Get; + + /// The id type that can generate sub-accounts + type Id: Encode + Decode + TypeId; + + /// The root id used to derive sub-accounts. These sub-accounts will be used to bond the tokens + type RootId: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Event1 { who: T::AccountId }, + } + + #[pallet::error] + pub enum Error { + Error1, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// If sub-account has all the tokens unbonded, it will transfer everything including ED back to the treasury + #[pallet::call_index(0)] + #[pallet::weight(Weight::zero())] + pub fn transfer_back_to_treasury(origin: OriginFor, derivation_path: u32) -> DispatchResult { + let _caller = ensure_signed(origin)?; + + let treasury = T::Treasury::get(); + let bonding_account: AccountIdOf = T::RootId::get().into_sub_account_truncating(derivation_path); + if T::BondingToken::total_balance_on_hold(&bonding_account).is_zero() { + // Transfer everything back to the treasury + T::BondingToken::transfer( + &bonding_account, + &treasury, + T::BondingToken::balance(&bonding_account), + Preservation::Expendable, + )?; + Ok(()) + } else { + // Bonded tokens are still locked + return Err(Error::::Error1.into()); + } + } + } +} diff --git a/pallets/proxy-bonding/src/mock.rs b/pallets/proxy-bonding/src/mock.rs new file mode 100644 index 000000000..e6d37f8e7 --- /dev/null +++ b/pallets/proxy-bonding/src/mock.rs @@ -0,0 +1,126 @@ +use frame_support::traits::WithdrawReasons; +use std::cell::RefCell; +use std::collections::BTreeMap; +use frame_support::{derive_impl, weights::constants::RocksDbWeight, PalletId}; +use frame_system::{mocking::MockBlock, GenesisConfig}; +use sp_runtime::{traits::ConstU64, BuildStorage, FixedU128, Perbill}; +use sp_runtime::traits::{parameter_types, ConstU32, ConstU8, Identity}; +use crate::AccountIdOf; + +// Configure a mock runtime to test the pallet. +#[frame_support::runtime] +mod test_runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct TestRuntime; + + #[runtime::pallet_index(0)] + pub type System = frame_system; + + #[runtime::pallet_index(1)] + pub type Balances = pallet_balances; + + #[runtime::pallet_index(2)] + pub type Assets = pallet_assets; + + #[runtime::pallet_index(3)] + pub type LinearRelease = pallet_linear_release; + + #[runtime::pallet_index(4)] + pub type ProxyBonding = crate; + +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for TestRuntime { + type Block = MockBlock; + type BlockHashCount = ConstU64<250>; + type DbWeight = RocksDbWeight; + type Nonce = u64; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for TestRuntime { +} + +#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)] +impl pallet_assets::Config for TestRuntime { +} + +parameter_types! { + pub const MinVestedTransfer: u64 = 256 * 2; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); + pub BenchmarkReason: RuntimeHoldReason = RuntimeHoldReason::Reason; +} +impl pallet_linear_release::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type Balance = ::Balance; + type RuntimeHoldReason = RuntimeHoldReason; + type Currency = Balances; + type BlockNumberToBalance = Identity; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type MinVestedTransfer = MinVestedTransfer; + const MAX_VESTING_SCHEDULES: u32 = 10; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkReason = BenchmarkReason; +} + +parameter_types! { + pub FeePercentage: Perbill = Perbill::from_percent(1); + pub Treasury: sp_runtime::AccountId32 = [0u8; 32]; + pub RootId: PalletId = PalletId(*b"treasury"); +} + +thread_local! { + pub static PRICE_MAP: RefCell> = RefCell::new(BTreeMap::from_iter(vec![ + (3344, FixedU128::from_float(0.5f64)), // Native Token + (1337, FixedU128::from_float(1f64)), // Fee Asset + ])); +} +pub struct ConstPriceProvider; +impl ProvideAssetPrice for ConstPriceProvider { + type AssetId = u32; + type Price = FixedU128; + + fn get_price(asset_id: u32) -> Option { + PRICE_MAP.with(|price_map| price_map.borrow().get(&asset_id).cloned()) + } +} + +impl ConstPriceProvider { + pub fn set_price(asset_id: u32, price: FixedU128) { + PRICE_MAP.with(|price_map| { + price_map.borrow_mut().insert(asset_id, price); + }); + } +} +impl crate::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type BondingToken = Balances; + type BondingTokenDecimals = ConstU8<10>; + type UsdDecimals = ConstU8<6>; + type BondingTokenId = ConstU32<3344>; + type FeeToken = Assets; + type FeePercentage = FeePercentage; + type PriceProvider = ConstPriceProvider; + type Treasury = Treasury; + type Id = PalletId; + type RootId = RootId; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/pallets/proxy-bonding/src/tests.rs b/pallets/proxy-bonding/src/tests.rs new file mode 100644 index 000000000..b7ed0c506 --- /dev/null +++ b/pallets/proxy-bonding/src/tests.rs @@ -0,0 +1,8 @@ +use crate::mock::*; + +#[test] +fn test() { + new_test_ext().execute_with(|| { + + }); +} \ No newline at end of file diff --git a/polimec-common/common/src/lib.rs b/polimec-common/common/src/lib.rs index f984c0a0e..925b2d6ae 100644 --- a/polimec-common/common/src/lib.rs +++ b/polimec-common/common/src/lib.rs @@ -17,7 +17,10 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{pallet_prelude::*, traits::tokens::fungible}; -use sp_runtime::RuntimeDebug; +use sp_runtime::{ + traits::{CheckedDiv, CheckedMul}, + FixedPointNumber, RuntimeDebug, +}; use sp_std::prelude::*; pub use xcm::v4::{opaque::Xcm, Assets, Location, QueryId, SendError, SendResult, SendXcm, XcmHash}; @@ -235,3 +238,47 @@ impl SendXcm for DummyXcmSender { Ok([0u8; 32]) } } + +pub trait ProvideAssetPrice { + type AssetId; + type Price: FixedPointNumber; + fn get_price(asset_id: Self::AssetId) -> Option; + + /// Prices define the relationship between USD/Asset. When to and from that asset, we need to be aware that they might + /// have different decimals. This function calculates the relationship having in mind the decimals. For example: + /// if the price is 2.5, our underlying USD unit has 6 decimals, and the asset has 8 decimals, the price will be + /// 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, + /// we would do `0.025(USD/Asset)FixedPointNumber * 20_000_000_00(Asset)u128 = 50_000_000` which is 50 USD with 6 decimals + fn calculate_decimals_aware_price( + original_price: Self::Price, + usd_decimals: u8, + asset_decimals: u8, + ) -> Option { + let usd_unit = 10u128.checked_pow(usd_decimals.into())?; + let usd_price_with_decimals = original_price.checked_mul_int(usd_unit)?; + let asset_unit = 10u128.checked_pow(asset_decimals.into())?; + + Self::Price::checked_from_rational(usd_price_with_decimals, asset_unit) + } + + fn convert_back_to_normal_price( + decimals_aware_price: Self::Price, + usd_decimals: u8, + asset_decimals: u8, + ) -> Option { + let abs_diff: u32 = asset_decimals.abs_diff(usd_decimals).into(); + let abs_diff_unit = 10u128.checked_pow(abs_diff)?; + // 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 + let abs_diff_fixed = Self::Price::checked_from_rational(abs_diff_unit, 1)?; + if usd_decimals > asset_decimals { + decimals_aware_price.checked_div(&abs_diff_fixed) + } else { + decimals_aware_price.checked_mul(&abs_diff_fixed) + } + } + + fn get_decimals_aware_price(asset_id: Self::AssetId, usd_decimals: u8, asset_decimals: u8) -> Option { + let original_price = Self::get_price(asset_id)?; + Self::calculate_decimals_aware_price(original_price, usd_decimals, asset_decimals) + } +} diff --git a/runtimes/shared-configuration/src/assets.rs b/runtimes/shared-configuration/src/assets.rs index 0b3241d43..aff29a260 100644 --- a/runtimes/shared-configuration/src/assets.rs +++ b/runtimes/shared-configuration/src/assets.rs @@ -21,7 +21,7 @@ use crate::{ use core::marker::PhantomData; use frame_support::{parameter_types, PalletId}; use orml_traits::DataProvider; -use pallet_funding::traits::ProvideAssetPrice; +use polimec_common::ProvideAssetPrice; use parachains_common::DAYS; use polimec_common::credentials::Cid; use sp_arithmetic::FixedPointNumber;