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

CU-1wty9p0 oracle api clarification #462

Merged
merged 10 commits into from
Jan 18, 2022
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