Skip to content
Merged
47 changes: 25 additions & 22 deletions frame/composable-traits/src/currency.rs
Original file line number Diff line number Diff line change
@@ -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<T: From<u64>>(&self) -> T {
T::from(10_u64.pow(self.decimals()))
}
fn milli<T: From<u64> + Div<Output = T>>(&self) -> T {
self.unit::<T>() / 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
Expand All @@ -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<CurrencyId> {
fn create() -> Result<CurrencyId, DispatchError>;
}

/// Local presentation of asset information.
/// Most pallets do not need it.
pub trait LocalAssets<MayBeAssetId> {
/// 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<Exponent, DispatchError>;

/// 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<T: From<u64>>(currency_id: MayBeAssetId) -> Result<T, DispatchError> {
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<MayBeAssetId> LocalAssets<MayBeAssetId> for () {
fn decimals(_currency_id: MayBeAssetId) -> Result<Exponent, DispatchError> {
Ok(0)
}
}

pub trait BalanceLike:
AtLeast32BitUnsigned
+ FullCodec
Expand Down
23 changes: 17 additions & 6 deletions frame/composable-traits/src/defi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,26 @@ impl<AssetId: PartialEq, Balance: PartialOrd + Zero + SafeArithmetic> Sell<Asset
}
}

/// given `base`, how much `quote` needed for unit
/// see [currency pair](https://www.investopedia.com/terms/c/currencypair.asp)
/// Pair with same base and quote is considered valid as it allows to have mixer, money laundering
/// like behavior.
/// See [currency pair](https://www.investopedia.com/terms/c/currencypair.asp)
/// Pair with same `base` and `quote` is considered valid as it allows to have mixer, money
/// laundering like behavior.
/// Can be used with Oracles, DEXes.
/// Example, can do - give `base`, how much `quote` needed for unit.
/// Can be local `Copy` `AssetId` or remote XCM asset id pair.
#[repr(C)]
#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq)]
pub struct CurrencyPair<AssetId> {
/// 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<AssetId: Copy> Copy for CurrencyPair<AssetId> {}

impl<AssetId: PartialEq> CurrencyPair<AssetId> {
pub fn new(base: AssetId, quote: AssetId) -> Self {
Self { base, quote }
Expand All @@ -92,6 +99,10 @@ impl<AssetId: PartialEq> CurrencyPair<AssetId> {
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<AssetId: PartialEq> AsRef<[AssetId]> for CurrencyPair<AssetId> {
Expand Down
2 changes: 1 addition & 1 deletion frame/composable-traits/src/lending/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ pub trait Lending {
borrow_amount: Self::Balance,
) -> Result<Self::Balance, DispatchError>;

/// 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
Expand Down
58 changes: 41 additions & 17 deletions frame/composable-traits/src/oracle.rs
Original file line number Diff line number Diff line change
@@ -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<PriceValue, BlockNumber> {
pub price: PriceValue,
Expand All @@ -12,21 +12,30 @@ pub struct Price<PriceValue, BlockNumber> {
/// 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<u64>;
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<Self::AssetId>;
/// 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
Expand All @@ -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<Price<Self::Balance, Self::Timestamp>, 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<bool, DispatchError> {
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<Self::Balance>,
) -> Result<Self::Balance, DispatchError>;

/// 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<Self::AssetId>) -> Result<FixedU128, DispatchError>;
}
Empty file removed frame/currency-factory/README.md
Empty file.
11 changes: 10 additions & 1 deletion frame/currency-factory/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -85,4 +87,11 @@ pub mod pallet {
})
}
}

impl<T: Config> LocalAssets<T::DynamicCurrencyId> for Pallet<T> {
fn decimals(_currency_id: T::DynamicCurrencyId) -> Result<Exponent, DispatchError> {
// All assets are normalized to 12 decimals.
Ok(12)
}
}
}
15 changes: 1 addition & 14 deletions frame/dutch-auction/src/mock/currency.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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<Self, DispatchError> {
match self {
Expand Down
2 changes: 1 addition & 1 deletion frame/lending/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Loading