diff --git a/frame/composable-traits/src/currency.rs b/frame/composable-traits/src/currency.rs index 7ff9b6d271e..10b7d95f376 100644 --- a/frame/composable-traits/src/currency.rs +++ b/frame/composable-traits/src/currency.rs @@ -1,33 +1,12 @@ -use core::ops::Div; - use codec::FullCodec; use frame_support::pallet_prelude::*; use scale_info::TypeInfo; use sp_runtime::traits::AtLeast32BitUnsigned; use sp_std::fmt::Debug; +/// really u8, but easy to do math operations pub type Exponent = u32; -/// A asset that can be priced. -pub trait PriceableAsset -where - Self: Copy, -{ - fn decimals(&self) -> Exponent; - fn unit>(&self) -> T { - T::from(10_u64.pow(self.decimals())) - } - fn milli + Div>(&self) -> T { - self.unit::() / T::from(1000_u64) - } -} - -impl PriceableAsset for u128 { - fn decimals(&self) -> Exponent { - 0 - } -} - /* NOTE(hussein-aitlahcen): I initially added a generic type to index into the generatable sub-range but realised it was overkill. Perhaps it will be required later if we want to differentiate multiple sub-ranges @@ -46,10 +25,34 @@ where /// Creates a new asset, compatible with [`MultiCurrency`](https://docs.rs/orml-traits/0.4.0/orml_traits/currency/trait.MultiCurrency.html). /// The implementor should ensure that a new `CurrencyId` is created and collisions are avoided. +/// Is about Local assets representations. These may differ remotely. pub trait CurrencyFactory { fn create() -> Result; } +/// Local presentation of asset information. +/// Most pallets do not need it. +pub trait LocalAssets { + /// decimals of of big unit over minimal unit. + /// orml also has separate trait on Balances to inspect decimals, that is not on type it self + fn decimals(currency_id: MayBeAssetId) -> Result; + + /// Amount which humans operate as `1` usually. + /// Amount is probably priceable by Oracles. + /// Amount resonably higher than minimal tradeable amount or minial trading step on DEX. + fn unit>(currency_id: MayBeAssetId) -> Result { + let exponent = Self::decimals(currency_id)?; + Ok(10_u64.pow(exponent).into()) + } +} + +/// when we store assets in native form to chain in smallest units or for mock in tests +impl LocalAssets for () { + fn decimals(_currency_id: MayBeAssetId) -> Result { + Ok(0) + } +} + pub trait BalanceLike: AtLeast32BitUnsigned + FullCodec diff --git a/frame/composable-traits/src/defi.rs b/frame/composable-traits/src/defi.rs index b8615aedb89..433855bc9bc 100644 --- a/frame/composable-traits/src/defi.rs +++ b/frame/composable-traits/src/defi.rs @@ -59,19 +59,26 @@ impl Sell { - /// See [Base Currency](https://www.investopedia.com/terms/b/basecurrency.asp) + /// See [Base Currency](https://www.investopedia.com/terms/b/basecurrency.asp). + /// Also can be named `native`(to the market) currency. pub base: AssetId, - /// counter currency + /// Counter currency. + /// Also can be named `price` currency. pub quote: AssetId, } +/// `AssetId` is Copy, than consider pair to be Copy +impl Copy for CurrencyPair {} + impl CurrencyPair { pub fn new(base: AssetId, quote: AssetId) -> Self { Self { base, quote } @@ -92,6 +99,10 @@ impl CurrencyPair { pub fn as_slice(&self) -> &[AssetId] { unsafe { sp_std::slice::from_raw_parts(self as *const Self as *const AssetId, 2) } } + + pub fn reverse(&mut self) { + sp_std::mem::swap(&mut self.quote, &mut self.base) + } } impl AsRef<[AssetId]> for CurrencyPair { diff --git a/frame/composable-traits/src/lending/mod.rs b/frame/composable-traits/src/lending/mod.rs index 7c7a06c7969..b0261d83df0 100644 --- a/frame/composable-traits/src/lending/mod.rs +++ b/frame/composable-traits/src/lending/mod.rs @@ -192,7 +192,7 @@ pub trait Lending { borrow_amount: Self::Balance, ) -> Result; - /// Returns the borrow limit for an account. + /// Returns the borrow limit for an account in `Oracle` price. /// Calculation uses indexes from start of block time. /// Depends on overall collateral put by user into vault. /// This borrow limit of specific user, depends only on prices and users collateral, not on diff --git a/frame/composable-traits/src/oracle.rs b/frame/composable-traits/src/oracle.rs index e75197b8839..56f7e8b548e 100644 --- a/frame/composable-traits/src/oracle.rs +++ b/frame/composable-traits/src/oracle.rs @@ -1,8 +1,8 @@ +use crate::{currency::LocalAssets, defi::CurrencyPair}; use frame_support::{dispatch::DispatchError, pallet_prelude::*}; +use sp_runtime::FixedU128; use sp_std::vec::Vec; -use crate::currency::PriceableAsset; - #[derive(Encode, Decode, Default, Debug, PartialEq)] pub struct Price { pub price: PriceValue, @@ -12,21 +12,30 @@ pub struct Price { /// An object that is able to provide an asset price. /// Important: the current price-feed is providing prices in USDT only. pub trait Oracle { - type AssetId: PriceableAsset; + type AssetId: Copy; type Balance: From; type Timestamp; - - /// Quote the `amount` of `asset` in USDT cent. - /// Error is returned if `asset` not supported or price information not available. - - /// Assuming we have a price `p` for an unit (not smallest) of `asset` in USDT cents. - /// Let `k` be the number of decimals for `asset`. - /// The price of an amount `a` of the smallest possible unit of `asset` is: - /// p * a / 10^k - /// e.g. for BTC, the price is expressed for 1 BTC, but the amount is in sats: + type LocalAssets: LocalAssets; + /// Quote the `amount` of `asset_id` in normalized currency unit cent. Default is USDT Cent. + /// Which is 0.01 of USDT. `Result::Err` is returned if `asset_id` not supported or price + /// information not available. + /// + /// # Normal assets + /// + /// Assuming we have a price `price` for an unit (not smallest) of `asset_id` in USDT cents. + /// Let `decimals` be the number of decimals for `asset_id` as given by + /// `CurrencyFactory::decimals` The price of an amount `amount` of the smallest possible unit of + /// `asset_id` is: `price * amount / 10^decimals` + /// + /// + /// E.g. for BTC, the price is expressed for 1 BTC, but the amount is in sats: /// 1 BTC = 10^8 sats - /// get_price(BTC, 1_00000000) = price(1BTC) * 1_00000000 / 10^8 = $50000 - + /// So that: + /// `get_price(BTC, 1_00000000) = price(1BTC) * 1_00000000 / 10^8 = $50_000 = 5_000_000 USDT + /// cents` + /// + /// # Diluted assets + /// /// Implementation ensure that a LP token price can be resolved as long as the base asset price /// is resolvable. ///```haskell @@ -39,18 +48,33 @@ pub trait Oracle { /// price (Vaulted base stock_dilution_rate) = price base * stock_dilution_rate /// ``` fn get_price( - asset: Self::AssetId, + asset_id: Self::AssetId, amount: Self::Balance, ) -> Result, DispatchError>; - /// Check whether the provided `asset` is supported (a.k.a. a price can be computed) by the + /// Check whether the provided `asset_id` is supported (a.k.a. a price can be computed) by the /// oracle. fn is_supported(asset: Self::AssetId) -> Result { - Self::get_price(asset, asset.unit()).map(|_| true) + let exponent = Self::LocalAssets::decimals(asset)?; + let unit: Self::Balance = 10_u64.pow(exponent).into(); + Self::get_price(asset, unit).map(|_| true) } + /// Time Weighted Average Price fn get_twap( of: Self::AssetId, weighting: Vec, ) -> Result; + + /// Up to oracle how it decides ratio. + /// If there is no direct trading pair, can estimate via common pair (to which all currencies + /// are normalized). General formula + /// ```rust + /// let base_in_common = 1000.0; + /// let quote_in_common = 100.0; + /// let ratio = base_in_common / quote_in_common; // 10.0 + /// let base_amount = 3.0; + /// let needed_base_for_quote = base_amount * ratio; // 300.0 + /// ``` + fn get_ratio(pair: CurrencyPair) -> Result; } diff --git a/frame/currency-factory/README.md b/frame/currency-factory/README.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/frame/currency-factory/src/lib.rs b/frame/currency-factory/src/lib.rs index 5d52b4aa32a..95628952a21 100644 --- a/frame/currency-factory/src/lib.rs +++ b/frame/currency-factory/src/lib.rs @@ -1,3 +1,5 @@ +//! Overview +//! Allows to add new assets internally. User facing mutating API is provided by other pallets. #![cfg_attr( not(test), warn( @@ -37,7 +39,7 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use codec::FullCodec; - use composable_traits::currency::{CurrencyFactory, DynamicCurrencyId}; + use composable_traits::currency::{CurrencyFactory, DynamicCurrencyId, Exponent, LocalAssets}; use frame_support::{pallet_prelude::*, PalletId}; use scale_info::TypeInfo; @@ -85,4 +87,11 @@ pub mod pallet { }) } } + + impl LocalAssets for Pallet { + fn decimals(_currency_id: T::DynamicCurrencyId) -> Result { + // All assets are normalized to 12 decimals. + Ok(12) + } + } } diff --git a/frame/dutch-auction/src/mock/currency.rs b/frame/dutch-auction/src/mock/currency.rs index 139e405b087..3c985f5c649 100644 --- a/frame/dutch-auction/src/mock/currency.rs +++ b/frame/dutch-auction/src/mock/currency.rs @@ -1,4 +1,4 @@ -use composable_traits::currency::{DynamicCurrencyId, PriceableAsset}; +use composable_traits::currency::DynamicCurrencyId; use frame_support::parameter_types; use scale_info::TypeInfo; use sp_runtime::{ArithmeticError, DispatchError}; @@ -33,19 +33,6 @@ impl Default for CurrencyId { } } -impl PriceableAsset for CurrencyId { - fn decimals(&self) -> composable_traits::currency::Exponent { - match self { - CurrencyId::PICA => 0, - CurrencyId::BTC => 8, - CurrencyId::ETH => 18, - CurrencyId::LTC => 8, - CurrencyId::USDT => 2, - CurrencyId::LpToken(_) => 0, - } - } -} - impl DynamicCurrencyId for CurrencyId { fn next(self) -> Result { match self { diff --git a/frame/lending/README.md b/frame/lending/README.md index 003c904f2e0..95a741a0c5d 100644 --- a/frame/lending/README.md +++ b/frame/lending/README.md @@ -1,6 +1,6 @@ -https://composablefinance.atlassian.net/wiki/spaces/COM/pages/2916374/Lending +# [Overview](https://app.clickup.com/20465559/v/dc/kghwq-20761/kghwq-3621) ## What diff --git a/frame/lending/src/lib.rs b/frame/lending/src/lib.rs index 1e8962ff822..8dae973b6ae 100644 --- a/frame/lending/src/lib.rs +++ b/frame/lending/src/lib.rs @@ -54,13 +54,13 @@ pub mod pallet { use crate::{models::BorrowerData, weights::WeightInfo}; use codec::{Codec, FullCodec}; use composable_traits::{ - currency::{CurrencyFactory, PriceableAsset}, + currency::CurrencyFactory, defi::Rate, lending::{ math::*, BorrowAmountOf, CollateralLpAmountOf, Lending, MarketConfig, MarketConfigInput, }, liquidation::Liquidation, - loans::{DurationSeconds, PriceStructure, Timestamp}, + loans::{DurationSeconds, Timestamp}, math::{LiftedFixedBalance, SafeArithmetic}, oracle::Oracle, vault::{Deposit, FundsAvailability, StrategicVault, Vault, VaultConfig}, @@ -86,7 +86,7 @@ pub mod pallet { AccountIdConversion, AtLeast32BitUnsigned, CheckedAdd, CheckedMul, CheckedSub, One, Saturating, Zero, }, - ArithmeticError, FixedPointNumber, FixedPointOperand, FixedU128, + ArithmeticError, DispatchError, FixedPointNumber, FixedPointOperand, FixedU128, KeyTypeId as CryptoKeyTypeId, Percent, Perquintill, }; use sp_std::{fmt::Debug, vec, vec::Vec}; @@ -177,8 +177,7 @@ pub mod pallet { + MaybeSerializeDeserialize + Debug + Default - + TypeInfo - + PriceableAsset; + + TypeInfo; type Balance: Default + Parameter @@ -242,8 +241,7 @@ pub mod pallet { + From + Debug + Default - + TypeInfo - + PriceableAsset; + + TypeInfo; type Balance: Default + Parameter @@ -381,6 +379,7 @@ pub mod pallet { RepayAmountMustBeGraterThanZero, ExceedLendingCount, LiquidationFailed, + BorrowerDataCalculationFailed, } #[pallet::event] @@ -818,21 +817,7 @@ pub mod pallet { account: &::AccountId, ) -> Result<(), DispatchError> { if Self::should_liquidate(market_id, account)? { - let market = Self::get_market(market_id)?; - let borrow_asset = T::Vault::asset_id(&market.borrow)?; - let collateral_to_liquidate = Self::collateral_of_account(market_id, account)?; - let collateral_price = - Self::get_price(market.collateral, market.collateral.unit())?; - let source_target_account = Self::account_id(market_id); - T::Liquidation::liquidate( - &source_target_account, - market.collateral, - PriceStructure::new(collateral_price), - borrow_asset, - &source_target_account, - collateral_to_liquidate, - ) - .map(|_| ()) + Err(DispatchError::Other("TODO: work happens in other branch")) } else { Ok(()) } @@ -895,6 +880,10 @@ pub mod pallet { } else { errors.iter().for_each(|e| { if let Err(e) = e { + #[cfg(test)] + { + panic!("test failed with {:?}", e); + } log::error!( "This should never happen, could not initialize block!!! {:#?} {:#?}", block_number, @@ -1009,17 +998,21 @@ pub mod pallet { } let borrow_asset = T::Vault::asset_id(&market.borrow)?; - let borrow_limit_value = Self::get_borrow_limit(market_id, debt_owner)?; + + let borrow_limit = Self::get_borrow_limit(market_id, debt_owner)?; let borrow_amount_value = Self::get_price(borrow_asset, amount_to_borrow)?; ensure!( - borrow_limit_value >= borrow_amount_value, + borrow_limit >= borrow_amount_value, Error::::NotEnoughCollateralToBorrowAmount ); - ensure!( - ::Currency::can_withdraw(asset_id, market_account, amount_to_borrow) - .into_result() - .is_ok(), + ::Currency::can_withdraw( + borrow_asset, + market_account, + amount_to_borrow + ) + .into_result() + .is_ok(), Error::::NotEnoughBorrowAsset, ); ensure!( @@ -1349,7 +1342,6 @@ pub mod pallet { DebtMarkets::::try_get(market_id).map_err(|_| Error::::MarketDoesNotExist)?; let account_debt = DebtIndex::::get(market_id, account); - match account_debt { Some(account_interest_index) => { let principal = T::MarketDebtCurrency::balance_on_hold(debt_asset_id, account); @@ -1400,7 +1392,7 @@ pub mod pallet { let borrower = Self::create_borrower_data(market_id, account)?; Ok(borrower .borrow_for_collateral() - .map_err(|_| Error::::NotEnoughCollateralToBorrowAmount)? + .map_err(|_| Error::::BorrowerDataCalculationFailed)? .checked_mul_int(1_u64) .ok_or(ArithmeticError::Overflow)? .into()) diff --git a/frame/lending/src/mocks/mod.rs b/frame/lending/src/mocks/mod.rs index 2cf7f4e7685..5700c5eaa6a 100644 --- a/frame/lending/src/mocks/mod.rs +++ b/frame/lending/src/mocks/mod.rs @@ -1,6 +1,6 @@ use crate::{self as pallet_lending, *}; use composable_traits::{ - currency::{DynamicCurrencyId, Exponent, PriceableAsset}, + currency::{DynamicCurrencyId, Exponent}, defi::DeFiComposableConfig, governance::{GovernanceRegistry, SignedRawOrigin}, }; @@ -94,19 +94,6 @@ impl Default for MockCurrencyId { } } -impl PriceableAsset for MockCurrencyId { - fn decimals(&self) -> Exponent { - match self { - MockCurrencyId::PICA => 0, - MockCurrencyId::BTC => 8, - MockCurrencyId::ETH => 18, - MockCurrencyId::LTC => 8, - MockCurrencyId::USDT => 2, - MockCurrencyId::LpToken(_) => 0, - } - } -} - impl DynamicCurrencyId for MockCurrencyId { fn next(self) -> Result { match self { diff --git a/frame/lending/src/mocks/oracle.rs b/frame/lending/src/mocks/oracle.rs index a277ccb3d5b..05c16d04f26 100644 --- a/frame/lending/src/mocks/oracle.rs +++ b/frame/lending/src/mocks/oracle.rs @@ -4,12 +4,14 @@ pub use pallet::*; pub mod pallet { use codec::Codec; use composable_traits::{ - currency::PriceableAsset, + currency::LocalAssets, oracle::{Oracle, Price}, vault::Vault, }; use frame_support::pallet_prelude::*; - use sp_runtime::{helpers_128bit::multiply_by_rational, ArithmeticError, FixedPointNumber}; + use sp_runtime::{ + helpers_128bit::multiply_by_rational, ArithmeticError, DispatchError, FixedPointNumber, + }; use sp_std::fmt::Debug; use crate::mocks::{Balance, MockCurrencyId}; @@ -47,6 +49,7 @@ pub mod pallet { type AssetId = MockCurrencyId; type Balance = Balance; type Timestamp = (); + type LocalAssets = (); fn get_price( asset: Self::AssetId, @@ -54,7 +57,7 @@ pub mod pallet { ) -> Result, DispatchError> { let derive_price = |p: u128, a: u128| { let e = 10_u128 - .checked_pow(asset.decimals()) + .checked_pow(Self::LocalAssets::decimals(asset)?) .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))?; let price = multiply_by_rational(p, a, e) .map_err(|_| DispatchError::Arithmetic(ArithmeticError::Overflow))?; @@ -103,5 +106,11 @@ pub mod pallet { ) -> Result { Ok(0_u32.into()) } + + fn get_ratio( + _pair: composable_traits::defi::CurrencyPair, + ) -> Result { + Err(DispatchError::Other("No implemented")) + } } } diff --git a/frame/lending/src/tests.rs b/frame/lending/src/tests.rs index 050b93e9d7f..ef9d070819b 100644 --- a/frame/lending/src/tests.rs +++ b/frame/lending/src/tests.rs @@ -3,16 +3,16 @@ use std::ops::Mul; use crate::{ accrue_interest_internal, mocks::{ - new_test_ext, process_block, AccountId, Balance, BlockNumber, Lending, MockCurrencyId, - Oracle, Origin, Test, Tokens, Vault, VaultId, ALICE, BOB, CHARLIE, MILLISECS_PER_BLOCK, - MINIMUM_BALANCE, UNRESERVED, + new_test_ext, process_block, AccountId, Balance, BlockNumber, Lending, LpTokenFactory, + MockCurrencyId, Oracle, Origin, Test, Tokens, Vault, VaultId, ALICE, BOB, CHARLIE, + MILLISECS_PER_BLOCK, MINIMUM_BALANCE, UNRESERVED, }, models::BorrowerData, Error, MarketIndex, }; use composable_tests_helpers::{prop_assert_acceptable_computation_error, prop_assert_ok}; use composable_traits::{ - currency::PriceableAsset, + currency::{CurrencyFactory, LocalAssets}, defi::Rate, lending::{math::*, MarketConfigInput}, math::LiftedFixedBalance, @@ -33,6 +33,7 @@ type CollateralAsset = MockCurrencyId; const DEFAULT_MARKET_VAULT_RESERVE: Perquintill = Perquintill::from_percent(10); const DEFAULT_MARKET_VAULT_STRATEGY_SHARE: Perquintill = Perquintill::from_percent(90); +const DEFAULT_COLLATERAL_FACTOR: u128 = 2; /// Create a very simple vault for the given currency, 100% is reserved. fn create_simple_vault( @@ -95,7 +96,7 @@ fn create_simple_market() -> (MarketIndex, BorrowAssetVault) { MockCurrencyId::USDT, *ALICE, DEFAULT_MARKET_VAULT_RESERVE, - NormalizedCollateralFactor::saturating_from_rational(200, 100), + NormalizedCollateralFactor::saturating_from_rational(DEFAULT_COLLATERAL_FACTOR * 100, 100), ) } @@ -244,7 +245,8 @@ fn accrue_interest_plotter() { #[cfg(feature = "visualization")] { use plotters::prelude::*; - let area = BitMapBackend::new("./accrue_interest.png", (1024, 768)).into_drawing_area(); + let area = + BitMapBackend::new("./accrue_interest_plotter.png", (1024, 768)).into_drawing_area(); area.fill(&WHITE).unwrap(); let mut chart = ChartBuilder::on(&area) @@ -269,22 +271,15 @@ fn accrue_interest_plotter() { #[test] fn test_borrow_repay_in_same_block() { new_test_ext().execute_with(|| { - let collateral_amount = 900000; + let collateral_amount = 900000000; let (market, vault) = create_simple_market(); - // Balance for ALICE - assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), 0); assert_ok!(Tokens::mint_into(MockCurrencyId::USDT, &ALICE, collateral_amount)); - assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), collateral_amount); assert_ok!(Lending::deposit_collateral_internal(&market, &ALICE, collateral_amount)); assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), 0); - // Balance of BTC for CHARLIE - // CHARLIE is only lender of BTC - let borrow_asset_deposit = collateral_amount; - assert_eq!(Tokens::balance(MockCurrencyId::BTC, &CHARLIE), 0); + let borrow_asset_deposit = 900000; assert_ok!(Tokens::mint_into(MockCurrencyId::BTC, &CHARLIE, borrow_asset_deposit)); - assert_eq!(Tokens::balance(MockCurrencyId::BTC, &CHARLIE), borrow_asset_deposit); assert_ok!(Vault::deposit(Origin::signed(*CHARLIE), vault, borrow_asset_deposit)); let mut total_cash = DEFAULT_MARKET_VAULT_STRATEGY_SHARE.mul(borrow_asset_deposit); @@ -294,8 +289,12 @@ fn test_borrow_repay_in_same_block() { process_block(i); } + let price = + |currency_id, amount| Oracle::get_price(currency_id, amount).expect("impossible").price; + assert_eq!(Lending::borrow_balance_current(&market, &ALICE), Ok(Some(0))); - let alice_limit = Lending::get_borrow_limit(&market, &ALICE).unwrap(); + let limit_normalized = Lending::get_borrow_limit(&market, &ALICE).unwrap(); + let alice_limit = limit_normalized / price(MockCurrencyId::BTC, 1); assert_eq!(Lending::total_cash(&market), Ok(total_cash)); process_block(1); assert_ok!(Lending::borrow_internal(&market, &ALICE, alice_limit / 4)); @@ -352,37 +351,32 @@ fn test_borrow_math() { } #[test] -fn test_borrow() { +fn borrow_flow() { new_test_ext().execute_with(|| { let (market, vault) = create_simple_market(); + let unit = 1_000_000_000; + Oracle::set_btc_price(50000); - Oracle::set_btc_price(50_000 * MockCurrencyId::USDT.unit::()); - - let btc_price = |x| { - Oracle::get_price(MockCurrencyId::BTC, x * MockCurrencyId::BTC.unit::()) - .expect("impossible") - .price - }; + let price = + |currency_id, amount| Oracle::get_price(currency_id, amount).expect("impossible").price; - let collateral_amount = 1_000_000 * MockCurrencyId::USDT.unit::(); + let alice_capable_btc = 100 * unit; + let collateral_amount = alice_capable_btc * price(MockCurrencyId::BTC, 1000) / + price(MockCurrencyId::USDT, 1000); assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), 0); assert_ok!(Tokens::mint_into(MockCurrencyId::USDT, &ALICE, collateral_amount)); - assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), collateral_amount); assert_ok!(Lending::deposit_collateral_internal(&market, &ALICE, collateral_amount)); - assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), 0); + let limit_normalized = Lending::get_borrow_limit(&market, &ALICE).unwrap(); + let limit = limit_normalized / price(MockCurrencyId::BTC, 1); + assert_eq!(limit, alice_capable_btc / DEFAULT_COLLATERAL_FACTOR); - let borrow_asset_deposit = 100 * MockCurrencyId::BTC.unit::(); + let borrow_asset_deposit = 100000 * unit; assert_eq!(Tokens::balance(MockCurrencyId::BTC, &CHARLIE), 0); assert_ok!(Tokens::mint_into(MockCurrencyId::BTC, &CHARLIE, borrow_asset_deposit)); assert_eq!(Tokens::balance(MockCurrencyId::BTC, &CHARLIE), borrow_asset_deposit); assert_ok!(Vault::deposit(Origin::signed(*CHARLIE), vault, borrow_asset_deposit)); assert_eq!(Tokens::balance(MockCurrencyId::BTC, &CHARLIE), 0); - let alice_limit = Lending::get_borrow_limit(&market, &ALICE).unwrap(); - - // Can only borrow $500_000 worth of BTC - assert_eq!(alice_limit, btc_price(10)); - // Allow the market to initialize it's account by withdrawing // from the vault for i in 1..2 { @@ -392,30 +386,40 @@ fn test_borrow() { let expected_cash = DEFAULT_MARKET_VAULT_STRATEGY_SHARE.mul(borrow_asset_deposit); assert_eq!(Lending::total_cash(&market), Ok(expected_cash)); - // Borrow a single BTC - let alice_borrow = MockCurrencyId::BTC.unit::(); + let alice_borrow = alice_capable_btc / DEFAULT_COLLATERAL_FACTOR / 10; assert_ok!(Lending::borrow_internal(&market, &ALICE, alice_borrow)); assert_eq!(Lending::total_cash(&market), Ok(expected_cash - alice_borrow)); assert_eq!(Lending::total_borrows(&market), Ok(alice_borrow)); + assert_eq!(Lending::total_interest_accurate(&market), Ok(0)); - for i in 2..10000 { + let limit_normalized = Lending::get_borrow_limit(&market, &ALICE).unwrap(); + let original_limit = limit_normalized / price(MockCurrencyId::BTC, 1); + + assert_eq!(original_limit, alice_capable_btc / DEFAULT_COLLATERAL_FACTOR - alice_borrow); + + let borrow = Lending::borrow_balance_current(&market, &ALICE).unwrap().unwrap(); + assert_eq!(borrow, alice_borrow); + let interest_before = Lending::total_interest_accurate(&market).unwrap(); + for i in 2..50 { process_block(i); } + let interest_after = Lending::total_interest_accurate(&market).unwrap(); - assert_eq!(Lending::total_interest_accurate(&market), Ok(3994235158863300000000)); + let limit_normalized = Lending::get_borrow_limit(&market, &ALICE).unwrap(); + let new_limit = limit_normalized / price(MockCurrencyId::BTC, 1); - // Interests mean that we cannot borrow the remaining 9 BTCs. + assert!(new_limit < original_limit); + + let borrow = Lending::borrow_balance_current(&market, &ALICE).unwrap().unwrap(); + + assert!(borrow > alice_borrow); assert_noop!( - Lending::borrow_internal(&market, &ALICE, 9 * MockCurrencyId::BTC.unit::()), + Lending::borrow_internal(&market, &ALICE, original_limit), Error::::NotEnoughCollateralToBorrowAmount ); // Borrow less because of interests. - assert_ok!(Lending::borrow_internal( - &market, - &ALICE, - 8 * MockCurrencyId::BTC.unit::() - )); + assert_ok!(Lending::borrow_internal(&market, &ALICE, new_limit,)); // More than one borrow request in same block is invalid. assert_noop!( @@ -429,22 +433,15 @@ fn test_borrow() { assert_ok!(Lending::deposit_collateral_internal(&market, &ALICE, collateral_amount)); let alice_limit = Lending::get_borrow_limit(&market, &ALICE).unwrap(); + assert!(price(MockCurrencyId::BTC, alice_capable_btc) > alice_limit); + assert!(alice_limit > price(MockCurrencyId::BTC, alice_borrow)); - // We didn't borrowed 9 because of interests, we added $ worth of 10 BTC, - // let x being our limit, it should be: 11>x>10 - assert!(btc_price(11) > alice_limit && alice_limit > btc_price(10)); - - // Try to borrow more than limit assert_noop!( - Lending::borrow_internal(&market, &ALICE, 11 * MockCurrencyId::BTC.unit::()), + Lending::borrow_internal(&market, &ALICE, alice_limit), Error::::NotEnoughCollateralToBorrowAmount ); - assert_ok!(Lending::borrow_internal( - &market, - &ALICE, - 10 * MockCurrencyId::BTC.unit::() - )); + assert_ok!(Lending::borrow_internal(&market, &ALICE, 10)); }); } @@ -453,8 +450,8 @@ fn test_vault_market_cannot_withdraw() { new_test_ext().execute_with(|| { let (market, vault_id) = create_simple_market(); - let deposit_usdt = 1_000_000 * MockCurrencyId::USDT.unit::(); - let deposit_btc = 10 * MockCurrencyId::BTC.unit::(); + let deposit_usdt = 1_000_000; + let deposit_btc = 10; assert_ok!(Tokens::mint_into(MockCurrencyId::USDT, &ALICE, deposit_usdt)); assert_ok!(Tokens::mint_into(MockCurrencyId::BTC, &ALICE, deposit_btc)); @@ -463,7 +460,7 @@ fn test_vault_market_cannot_withdraw() { // We don't even wait 1 block, which mean the market couldn't withdraw funds. assert_noop!( - Lending::borrow_internal(&market, &ALICE, MockCurrencyId::BTC.unit::()), + Lending::borrow_internal(&market, &ALICE, deposit_btc), Error::::NotEnoughBorrowAsset ); }); @@ -474,13 +471,13 @@ fn test_vault_market_can_withdraw() { new_test_ext().execute_with(|| { let (market, vault_id) = create_simple_market(); - let deposit_usdt = 1_000_000 * MockCurrencyId::USDT.unit::(); - let deposit_btc = 10 * MockCurrencyId::BTC.unit::(); - assert_ok!(Tokens::mint_into(MockCurrencyId::USDT, &ALICE, deposit_usdt)); - assert_ok!(Tokens::mint_into(MockCurrencyId::BTC, &ALICE, deposit_btc)); + let collateral = 1_000_000_000_000; + let borrow = 10; + assert_ok!(Tokens::mint_into(MockCurrencyId::USDT, &ALICE, collateral)); + assert_ok!(Tokens::mint_into(MockCurrencyId::BTC, &ALICE, borrow)); - assert_ok!(Vault::deposit(Origin::signed(*ALICE), vault_id, deposit_btc)); - assert_ok!(Lending::deposit_collateral_internal(&market, &ALICE, deposit_usdt)); + assert_ok!(Vault::deposit(Origin::signed(*ALICE), vault_id, borrow)); + assert_ok!(Lending::deposit_collateral_internal(&market, &ALICE, collateral)); for i in 1..2 { process_block(i); @@ -490,7 +487,7 @@ fn test_vault_market_can_withdraw() { assert_ok!(Lending::borrow_internal( &market, &ALICE, - MockCurrencyId::BTC.unit::() + borrow - 1 // DEFAULT_MARKET_VAULT_RESERVE ),); }); } @@ -498,8 +495,8 @@ fn test_vault_market_can_withdraw() { #[test] fn borrow_repay() { new_test_ext().execute_with(|| { - let alice_balance = 1_000_000 * MockCurrencyId::USDT.unit::(); - let bob_balance = 1_000_000 * MockCurrencyId::USDT.unit::(); + let alice_balance = 1_000_000; + let bob_balance = 1_000_000; let (market, vault) = create_simple_market(); @@ -517,7 +514,7 @@ fn borrow_repay() { assert_ok!(Lending::deposit_collateral_internal(&market, &BOB, bob_balance)); assert_eq!(Tokens::balance(MockCurrencyId::USDT, &BOB), 0); - let borrow_asset_deposit = 10 * MockCurrencyId::BTC.unit::(); + let borrow_asset_deposit = 10_000_000_000; assert_eq!(Tokens::balance(MockCurrencyId::BTC, &CHARLIE), 0); assert_ok!(Tokens::mint_into(MockCurrencyId::BTC, &CHARLIE, borrow_asset_deposit)); assert_eq!(Tokens::balance(MockCurrencyId::BTC, &CHARLIE), borrow_asset_deposit); @@ -531,7 +528,9 @@ fn borrow_repay() { // ALICE borrows assert_eq!(Lending::borrow_balance_current(&market, &ALICE), Ok(Some(0))); - let alice_limit = Lending::get_borrow_limit(&market, &ALICE).unwrap(); + let alice_limit_normalized = Lending::get_borrow_limit(&market, &ALICE).unwrap(); + let alice_limit = + alice_limit_normalized / Oracle::get_price(MockCurrencyId::BTC, 1).unwrap().price; assert_ok!(Lending::borrow_internal(&market, &ALICE, alice_limit)); for i in 2..10000 { @@ -540,11 +539,9 @@ fn borrow_repay() { // BOB borrows assert_eq!(Lending::borrow_balance_current(&market, &BOB), Ok(Some(0))); - assert_ok!(Lending::borrow_internal( - &market, - &BOB, - Lending::get_borrow_limit(&market, &BOB).expect("impossible") - )); + let limit_normalized = Lending::get_borrow_limit(&market, &BOB).unwrap(); + let limit = limit_normalized / Oracle::get_price(MockCurrencyId::BTC, 1).unwrap().price; + assert_ok!(Lending::borrow_internal(&market, &BOB, limit,)); let bob_limit = Lending::get_borrow_limit(&market, &BOB).unwrap(); @@ -587,9 +584,9 @@ fn test_liquidation() { NormalizedCollateralFactor::saturating_from_rational(2, 1), ); - Oracle::set_btc_price(100 * MockCurrencyId::USDT.unit::()); + Oracle::set_btc_price(100); - let two_btc_amount = 2 * MockCurrencyId::BTC.unit::(); + let two_btc_amount = 2; assert_eq!(Tokens::balance(MockCurrencyId::BTC, &ALICE), 0); assert_ok!(Tokens::mint_into(MockCurrencyId::BTC, &ALICE, two_btc_amount)); assert_eq!(Tokens::balance(MockCurrencyId::BTC, &ALICE), two_btc_amount); @@ -620,7 +617,7 @@ fn test_liquidation() { } // Collateral going down imply liquidation - Oracle::set_btc_price(99 * MockCurrencyId::USDT.unit::()); + Oracle::set_btc_price(99); assert_ok!(Lending::liquidate_internal(&market, &ALICE)); }); @@ -638,10 +635,10 @@ fn test_warn_soon_under_collaterized() { ); // 1 BTC = 100 USDT - Oracle::set_btc_price(100 * MockCurrencyId::USDT.unit::()); + Oracle::set_btc_price(100); // Deposit 2 BTC - let two_btc_amount = 2 * MockCurrencyId::BTC.unit::(); + let two_btc_amount = 2; assert_eq!(Tokens::balance(MockCurrencyId::BTC, &ALICE), 0); assert_ok!(Tokens::mint_into(MockCurrencyId::BTC, &ALICE, two_btc_amount)); assert_eq!(Tokens::balance(MockCurrencyId::BTC, &ALICE), two_btc_amount); @@ -671,20 +668,17 @@ fn test_warn_soon_under_collaterized() { = 10000 cents */ - assert_eq!( - Lending::get_borrow_limit(&market, &ALICE), - Ok(100 * MockCurrencyId::USDT.unit::()) - ); + assert_eq!(Lending::get_borrow_limit(&market, &ALICE), Ok(100)); // Borrow 80 USDT - let borrow_amount = 80 * MockCurrencyId::USDT.unit::(); + let borrow_amount = 80; assert_ok!(Lending::borrow_internal(&market, &ALICE, borrow_amount)); for i in 2..10000 { process_block(i); } - Oracle::set_btc_price(90 * MockCurrencyId::USDT.unit::()); + Oracle::set_btc_price(90); /* Collateral = 2*90 = 180 @@ -693,7 +687,7 @@ fn test_warn_soon_under_collaterized() { */ assert_eq!(Lending::soon_under_collaterized(&market, &ALICE), Ok(false)); - Oracle::set_btc_price(87 * MockCurrencyId::USDT.unit::()); + Oracle::set_btc_price(87); /* Collateral = 2*87 = 174 @@ -805,13 +799,13 @@ proptest! { new_test_ext().execute_with(|| { let (market, _vault) = create_simple_market(); prop_assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), 0); - prop_assert_ok!(Tokens::mint_into(MockCurrencyId::USDT, &ALICE, amount * MockCurrencyId::USDT.unit::())); - prop_assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), amount * MockCurrencyId::USDT.unit::()); + prop_assert_ok!(Tokens::mint_into(MockCurrencyId::USDT, &ALICE, amount )); + prop_assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), amount ); - prop_assert_ok!(Lending::deposit_collateral_internal(&market, &ALICE, amount * MockCurrencyId::USDT.unit::())); + prop_assert_ok!(Lending::deposit_collateral_internal(&market, &ALICE, amount )); prop_assert_eq!(Tokens::balance(MockCurrencyId::USDT, &ALICE), 0); prop_assert_eq!( - Lending::withdraw_collateral_internal(&market, &ALICE, amount * MockCurrencyId::USDT.unit::() + 1), + Lending::withdraw_collateral_internal(&market, &ALICE, amount + 1), Err(Error::::NotEnoughCollateral.into()) ); diff --git a/frame/oracle/src/lib.rs b/frame/oracle/src/lib.rs index d39cebd890f..326e1a858ff 100644 --- a/frame/oracle/src/lib.rs +++ b/frame/oracle/src/lib.rs @@ -30,7 +30,8 @@ pub mod pallet { pub use crate::weights::WeightInfo; use codec::{Codec, FullCodec}; use composable_traits::{ - currency::PriceableAsset, + currency::LocalAssets, + math::SafeArithmetic, oracle::{Oracle, Price as LastPrice}, }; use core::ops::{Div, Mul}; @@ -58,7 +59,8 @@ pub mod pallet { use sp_runtime::{ offchain::{http, Duration}, traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedMul, CheckedSub, Saturating, Zero}, - AccountId32, KeyTypeId as CryptoKeyTypeId, PerThing, Percent, RuntimeDebug, + AccountId32, FixedPointNumber, FixedU128, KeyTypeId as CryptoKeyTypeId, PerThing, Percent, + RuntimeDebug, }; use sp_std::{borrow::ToOwned, fmt::Debug, str, vec, vec::Vec}; @@ -111,8 +113,7 @@ pub mod pallet { + Into + Debug + Default - + TypeInfo - + PriceableAsset; + + TypeInfo; type PriceValue: Default + Parameter + Codec @@ -146,6 +147,7 @@ pub mod pallet { /// The weight information of this pallet. type WeightInfo: WeightInfo; + type LocalAssets: LocalAssets; } #[derive(Encode, Decode, Default, Debug, PartialEq, TypeInfo)] @@ -161,8 +163,10 @@ pub mod pallet { pub who: AccountId, } + // block timestamped value #[derive(Encode, Decode, Default, Debug, PartialEq, TypeInfo, Clone)] pub struct Price { + /// value pub price: PriceValue, pub block: BlockNumber, } @@ -371,16 +375,15 @@ pub mod pallet { type Balance = T::PriceValue; type AssetId = T::AssetId; type Timestamp = ::BlockNumber; + type LocalAssets = T::LocalAssets; - // TODO(hussein-aitlahcen): - // implement the amount based computation with decimals once it's been completely defined fn get_price( asset: Self::AssetId, - _amount: Self::Balance, + amount: Self::Balance, ) -> Result, DispatchError> { let Price { price, block } = Prices::::try_get(asset).map_err(|_| Error::::PriceNotFound)?; - Ok(LastPrice { price, block }) + Ok(LastPrice { price: price.safe_mul(&amount)?, block }) } fn get_twap( @@ -389,6 +392,22 @@ pub mod pallet { ) -> Result { Self::get_twap(of, weighting) } + + fn get_ratio( + pair: composable_traits::defi::CurrencyPair, + ) -> Result { + let base: u128 = + Self::get_price(pair.base, (10 ^ T::LocalAssets::decimals(pair.base)?).into())? + .price + .into(); + let quote: u128 = + Self::get_price(pair.quote, (10 ^ T::LocalAssets::decimals(pair.base)?).into())? + .price + .into(); + let base = FixedU128::saturating_from_integer(base); + let quote = FixedU128::saturating_from_integer(quote); + Ok(base.safe_div("e)?) + } } #[pallet::call] diff --git a/frame/oracle/src/mock.rs b/frame/oracle/src/mock.rs index 5aae71babc9..b7c74765481 100644 --- a/frame/oracle/src/mock.rs +++ b/frame/oracle/src/mock.rs @@ -133,6 +133,7 @@ impl pallet_oracle::Config for Test { type MaxAssetsCount = MaxAssetsCount; type MaxHistory = MaxHistory; type WeightInfo = (); + type LocalAssets = (); } // Build genesis storage according to the mock runtime. diff --git a/frame/oracle/src/tests.rs b/frame/oracle/src/tests.rs index d8a8ce4801a..1eabd4eb7b9 100644 --- a/frame/oracle/src/tests.rs +++ b/frame/oracle/src/tests.rs @@ -3,6 +3,7 @@ use crate::{ AssetInfo, Error, PrePrice, Price, Withdraw, *, }; use codec::Decode; +use composable_traits::defi::CurrencyPair; use frame_support::{ assert_noop, assert_ok, traits::{Currency, OnInitialize}, @@ -14,7 +15,7 @@ use sp_io::TestExternalities; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; use sp_runtime::{ traits::{BadOrigin, Zero}, - Percent, RuntimeAppPublic, + FixedPointNumber, FixedU128, Percent, RuntimeAppPublic, }; use std::sync::Arc; @@ -594,6 +595,54 @@ fn historic_pricing() { }); } +#[test] +fn price_of_amount() { + new_test_ext().execute_with(|| { + let value = 100500; + let id = 42; + let amount = 10000; + + let price = Price { price: value, block: System::block_number() }; + Prices::::insert(id, price); + let total_price = + ::get_price(id, amount).unwrap(); + + assert_eq!(total_price.price, value * amount) + }); +} +#[test] +fn ratio_human_case() { + new_test_ext().execute_with(|| { + let price = Price { price: 10000, block: System::block_number() }; + Prices::::insert(13, price); + let price = Price { price: 100, block: System::block_number() }; + Prices::::insert(42, price); + let mut pair = CurrencyPair::new(13, 42); + + let ratio = ::get_ratio(pair).unwrap(); + assert_eq!(ratio, FixedU128::saturating_from_integer(100)); + pair.reverse(); + let ratio = ::get_ratio(pair).unwrap(); + + assert_eq!(ratio, FixedU128::saturating_from_rational(1_u32, 100_u32)); + }) +} + +#[test] +fn ratio_base_is_way_less_smaller() { + new_test_ext().execute_with(|| { + let price = Price { price: 1, block: System::block_number() }; + Prices::::insert(13, price); + let price = Price { price: 10_u128.pow(12), block: System::block_number() }; + Prices::::insert(42, price); + let pair = CurrencyPair::new(13, 42); + + let ratio = ::get_ratio(pair).unwrap(); + + assert_eq!(ratio, FixedU128::saturating_from_rational(1, 1000000000000_u64)); + }) +} + #[test] fn get_twap() { new_test_ext().execute_with(|| { diff --git a/runtime/common/src/impls.rs b/runtime/common/src/impls.rs index 78c361ab098..82310533a0a 100644 --- a/runtime/common/src/impls.rs +++ b/runtime/common/src/impls.rs @@ -55,7 +55,6 @@ mod tests { use super::*; use crate::{Balance, BlockNumber, DAYS}; use collator_selection::IdentityCollator; - use composable_traits::currency::PriceableAsset; use frame_support::{ ord_parameter_types, parameter_types, traits::{Everything, FindAuthor, ValidatorRegistration}, diff --git a/runtime/composable/src/lib.rs b/runtime/composable/src/lib.rs index b681e165e11..ca3fdaca001 100644 --- a/runtime/composable/src/lib.rs +++ b/runtime/composable/src/lib.rs @@ -24,7 +24,6 @@ use common::{ CouncilInstance, EnsureRootOrHalfCouncil, Hash, Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, }; -use composable_traits::currency::PriceableAsset; use orml_traits::parameter_type_with_key; use primitives::currency::CurrencyId; use sp_api::impl_runtime_apis; diff --git a/runtime/dali/src/lib.rs b/runtime/dali/src/lib.rs index dd56d6e0c82..e30e22b4b44 100644 --- a/runtime/dali/src/lib.rs +++ b/runtime/dali/src/lib.rs @@ -25,7 +25,6 @@ use common::{ CouncilInstance, EnsureRootOrHalfCouncil, Hash, Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, }; -use composable_traits::currency::PriceableAsset; use orml_traits::parameter_type_with_key; use primitives::currency::CurrencyId; use sp_api::impl_runtime_apis; @@ -440,6 +439,7 @@ impl oracle::Config for Runtime { type MaxAssetsCount = MaxAssetsCount; type MaxHistory = MaxHistory; type WeightInfo = weights::oracle::WeightInfo; + type LocalAssets = Factory; } // Parachain stuff. diff --git a/runtime/picasso/src/lib.rs b/runtime/picasso/src/lib.rs index dda58be1c78..93f97451132 100644 --- a/runtime/picasso/src/lib.rs +++ b/runtime/picasso/src/lib.rs @@ -26,7 +26,6 @@ use common::{ CouncilInstance, EnsureRootOrHalfCouncil, Hash, Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, }; -use composable_traits::currency::PriceableAsset; use orml_traits::parameter_type_with_key; use primitives::currency::CurrencyId; use sp_api::impl_runtime_apis; diff --git a/runtime/primitives/src/currency.rs b/runtime/primitives/src/currency.rs index 5fc2a1555e2..73981396b48 100644 --- a/runtime/primitives/src/currency.rs +++ b/runtime/primitives/src/currency.rs @@ -1,68 +1,7 @@ //! CurrencyId implementation -/// Asset id as if it was deserialized, not necessary exists. -/// We could check asset id during serde, but that: -/// - will make serde setup complicated (need to write and consistently apply static singletons -/// to all places with asset id) -/// - validate will involve at minimum in memory cache call (in worth case db call) during -/// extrinsic invocation -/// - will need to disable this during calls when it is really no need for validation (new -/// currency mapping) -/// - normal path will pay price (validate each time), in instead when fail pays only (like -/// trying to transfer non existing asset id) -/// - we cannot guarantee existence of asset as it may be removed during transaction (so we -/// should make removal exclusive case) -/// -/// Given above we stick with possibly wrong asset id passed into API. -/// -/// # Assert id pallet design -/// ```ignore -/// pub trait MaximalConstGet { -/// const VALUE: T; -/// } -/// /// knows existing local assets and how to map them to simple numbers -/// pub trait LocalAssetsRegistry { -/// /// asset id which is exist from now in current block -/// /// valid does not means usable, it can be subject to deletion or not yet approved to be used -/// type AssetId : AssetIdLike + Into; -/// /// just id after serde -/// type MayBeAssetId : AssetIdLike + From; -/// /// assets which we well know and embedded into `enum`. -/// /// maximal of this is smaller than minimal `OtherAssetId` -/// /// can always convert to valid asset id -/// type WellKnownAssetId : MaximalConstGet + Into + Into + Decimals + TryFrom; -/// -/// /// Larger than maximal of `WellKnownAssetId` but smaller than minimal `DerivativeAssetId`. -/// type OtherAssetId : MinimalConstGet + MaximalConstGet + Into + Into; -/// /// allows to get next asset id -/// /// can consider split out producing assets interface into separate trait -/// type NextOtherAssetId = ErrorNext; -/// -/// /// locally diluted derivative and liquidity assets. -/// /// larger than maximal `OtherAssetId` -/// /// `Self::OtherAssetId` may be diluted(derived/wrapped), but only remote. -/// type DerivativeAssetId: MinimalConstGet + Into; -/// /// may consider split out asset producing trait -/// type NextDerivativeAssetId = ErrorNext; -/// -/// // note: fn to be replaced with Get or traits, just shortcuted here -/// -/// fn try_from>(value : N) -> Result; -/// /// one unique native asset id -/// fn native() -> Self::WellKnownAssetId; -/// -/// /// really u8, but easy to do math operations -/// /// ORML also has separate trait on Balances to inspect decimals, that is not on type it self -/// fn decimals(asset_id: Self::AssetId) -> u32; -/// } -/// /// read remote paths -/// /// registering is separate trait -/// pub trait RemoteAssetRegistry : LocalAssetsRegistry { -/// fn substrate(asset_id: Self::AssetId) -> Self:XcmPath; -/// fn remote(asset_id: Self::AssetId, network_id:) -> Self::Path; -/// } -/// ``` use codec::{CompactAs, Decode, Encode}; -use composable_traits::currency::{DynamicCurrencyId, Exponent, PriceableAsset}; +use composable_traits::currency::{DynamicCurrencyId, Exponent}; +use core::ops::Div; use scale_info::TypeInfo; use sp_runtime::{ArithmeticError, DispatchError, RuntimeDebug}; @@ -84,16 +23,18 @@ impl CurrencyId { pub const LAYR: CurrencyId = CurrencyId(2); pub const CROWD_LOAN: CurrencyId = CurrencyId(3); pub const KSM: CurrencyId = CurrencyId(4); - pub const LOCAL_LP_TOKEN_START: CurrencyId = CurrencyId(u128::MAX / 2); -} -/// All assets are normalized to 12 decimals. -impl PriceableAsset for CurrencyId { #[inline(always)] - fn decimals(&self) -> Exponent { + pub fn decimals(&self) -> Exponent { 12 } + pub fn unit>(&self) -> T { + T::from(10_u64.pow(self.decimals())) + } + pub fn milli + Div>(&self) -> T { + self.unit::() / T::from(1000_u64) + } } // NOTE(hussein-aitlahcen): we could add an index to DynamicCurrency to differentiate sub-ranges