Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/cargo_spellcheck.dic
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ tombstoned
u128
Wasm
Xcm
XCM
Dispatchable
14 changes: 7 additions & 7 deletions frame/assets/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ benchmarks! {
let asset_id: T::AssetId = ASSET_ID.into();
let dest = T::Lookup::unlookup(TO_ACCOUNT.into());
let amount: T::Balance = TRANSFER_AMOUNT.into();
T::MultiCurrency::mint_into(asset_id, &caller, amount).unwrap();
T::MultiCurrency::mint_into(asset_id, &caller, amount).expect("always can mint in test");
}: _(RawOrigin::Signed(caller), asset_id, dest, amount, true)

transfer_native {
let caller: T::AccountId = whitelisted_caller();
let dest = T::Lookup::unlookup(TO_ACCOUNT.into());
let amount: T::Balance = TRANSFER_AMOUNT.into();
T::NativeCurrency::mint_into(&caller, amount).unwrap();
T::NativeCurrency::mint_into(&caller, amount).expect("always can mint in test");
}: _(RawOrigin::Signed(caller), dest, amount, false)

force_transfer {
Expand All @@ -50,30 +50,30 @@ benchmarks! {
let from = T::Lookup::unlookup(FROM_ACCOUNT.into());
let dest = T::Lookup::unlookup(TO_ACCOUNT.into());
let amount: T::Balance = TRANSFER_AMOUNT.into();
T::MultiCurrency::mint_into(asset_id, &caller, amount).unwrap();
T::MultiCurrency::mint_into(asset_id, &caller, amount).expect("always can mint in test");
}: _(RawOrigin::Root, asset_id, from, dest, amount, false)

force_transfer_native {
let caller: T::AccountId = FROM_ACCOUNT.into();
let from = T::Lookup::unlookup(FROM_ACCOUNT.into());
let dest = T::Lookup::unlookup(TO_ACCOUNT.into());
let amount: T::Balance = TRANSFER_AMOUNT.into();
T::NativeCurrency::mint_into(&caller, amount).unwrap();
T::NativeCurrency::mint_into(&caller, amount).expect("always can mint in test");
}: _(RawOrigin::Root, from, dest, amount, false)

transfer_all {
let caller: T::AccountId = whitelisted_caller();
let asset_id: T::AssetId = ASSET_ID.into();
let dest = T::Lookup::unlookup(TO_ACCOUNT.into());
let amount: T::Balance = TRANSFER_AMOUNT.into();
T::MultiCurrency::mint_into(asset_id, &caller, amount).unwrap();
T::MultiCurrency::mint_into(asset_id, &caller, amount).expect("always can mint in test");
}: _(RawOrigin::Signed(caller), asset_id, dest, false)

transfer_all_native {
let caller: T::AccountId = whitelisted_caller();
let dest = T::Lookup::unlookup(TO_ACCOUNT.into());
let amount: T::Balance = TRANSFER_AMOUNT.into();
T::NativeCurrency::mint_into(&caller, amount).unwrap();
T::NativeCurrency::mint_into(&caller, amount).expect("always can mint in test");
}: _(RawOrigin::Signed(caller), dest, false)

mint_initialize {
Expand All @@ -98,7 +98,7 @@ benchmarks! {
let asset_id: T::AssetId = ASSET_ID.into();
let dest = T::Lookup::unlookup(TO_ACCOUNT.into());
let amount: T::Balance = TRANSFER_AMOUNT.into();
T::MultiCurrency::mint_into(asset_id, &caller, amount).unwrap();
T::MultiCurrency::mint_into(asset_id, &caller, amount).expect("always can mint in test");
}: _(RawOrigin::Root, asset_id, dest, amount)

}
Expand Down
35 changes: 2 additions & 33 deletions frame/composable-traits/src/auction.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,2 @@
use crate::loans::DurationSeconds;
use frame_support::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_runtime::Permill;

#[derive(Decode, Encode, Clone, TypeInfo, Debug, PartialEq)]
pub enum AuctionStepFunction {
/// default - direct pass through to dex without steps, just to satisfy defaults and reasonably
/// for testing
LinearDecrease(LinearDecrease),
StairstepExponentialDecrease(StairstepExponentialDecrease),
}

impl Default for AuctionStepFunction {
fn default() -> Self {
Self::LinearDecrease(Default::default())
}
}

#[derive(Default, Decode, Encode, Clone, TypeInfo, Debug, PartialEq)]
pub struct LinearDecrease {
/// Seconds after auction start when the price reaches zero
pub total: DurationSeconds,
}

#[derive(Default, Decode, Encode, Clone, TypeInfo, Debug, PartialEq)]
pub struct StairstepExponentialDecrease {
// Length of time between price drops
pub step: DurationSeconds,
// Per-step multiplicative factor, usually more than 50%, mostly closer to 100%, but not 100%.
// Drop per unit of `step`.
pub cut: Permill,
}
// TODO: how type alias to such generics can be achieved?
// pub type DutchAuction = SellEngine<AuctionStepFunction>;
16 changes: 15 additions & 1 deletion frame/composable-traits/src/currency.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use codec::FullCodec;
use frame_support::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_runtime::traits::AtLeast32BitUnsigned;
use sp_runtime::traits::{AtLeast32BitUnsigned, Zero};
use sp_std::fmt::Debug;

use crate::math::SafeArithmetic;

/// really u8, but easy to do math operations
pub type Exponent = u32;

Expand Down Expand Up @@ -77,6 +79,18 @@ impl<
{
}

/// limited counted number trait which maximal number is more than `u64`, but not more than `u128`,
/// so inner type is either u64 or u128 with helpers for producing `ArithmeticError`s instead of
/// `Option`s.
pub trait MathBalance:
PartialOrd + Zero + SafeArithmetic + Into<u128> + TryFrom<u128> + From<u64> + Copy
{
}
impl<T: PartialOrd + Zero + SafeArithmetic + Into<u128> + TryFrom<u128> + From<u64> + Copy>
MathBalance for T
{
}

// hack to imitate type alias until it is in stable
// named with like implying it is`like` is is necessary to be `AssetId`, but may be not enough (if
// something is `AssetIdLike` than it is not always asset)
Expand Down
91 changes: 70 additions & 21 deletions frame/composable-traits/src/defi.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
//! Common codes for defi pallets

//! Common codes and conventions for DeFi pallets
use codec::{Codec, Decode, Encode, FullCodec};
use frame_support::{pallet_prelude::MaybeSerializeDeserialize, Parameter};
use scale_info::TypeInfo;
use sp_runtime::{
helpers_128bit::multiply_by_rational,
traits::{CheckedAdd, CheckedMul, CheckedSub, Zero},
ArithmeticError, DispatchError, FixedPointOperand, FixedU128,
ArithmeticError, DispatchError, FixedPointNumber, FixedPointOperand, FixedU128,
};

use crate::{
currency::{AssetIdLike, BalanceLike},
math::{LiftedFixedBalance, SafeArithmetic},
};
use crate::currency::{AssetIdLike, BalanceLike, MathBalance};

#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq)]
pub struct Take<Balance> {
/// amount of `base`
pub amount: Balance,
/// direction depends on referenced order type
/// either minimal or maximal amount of `quote` for given unit of `base`
pub limit: Balance,
/// either minimal or maximal amount of `quote` for given `base`
/// depending on engine configuration, `limit` can be hard or flexible (change with time)
pub limit: LiftedFixedBalance,
}

impl<Balance: PartialOrd + Zero + SafeArithmetic> Take<Balance> {
impl<Balance: MathBalance> Take<Balance> {
pub fn is_valid(&self) -> bool {
self.amount > Balance::zero() && self.limit > Balance::zero()
self.amount > Balance::zero() && self.limit > Ratio::zero()
}
pub fn new(amount: Balance, limit: Balance) -> Self {

pub fn new(amount: Balance, limit: Ratio) -> Self {
Self { amount, limit }
}

pub fn quote_amount(&self) -> Result<Balance, ArithmeticError> {
self.amount.safe_mul(&self.limit)
pub fn quote_limit_amount(&self) -> Result<Balance, ArithmeticError> {
self.quote_amount(self.amount)
}

pub fn quote_amount(&self, amount: Balance) -> Result<Balance, ArithmeticError> {
let result = multiply_by_rational(amount.into(), self.limit.into_inner(), Ratio::DIV)
.map_err(|_| ArithmeticError::Overflow)?;
result.try_into().map_err(|_| ArithmeticError::Overflow)
}
}

Expand All @@ -42,15 +47,15 @@ pub struct Sell<AssetId, Balance> {
pub take: Take<Balance>,
}

impl<AssetId: PartialEq, Balance: PartialOrd + Zero + SafeArithmetic> Sell<AssetId, Balance> {
impl<AssetId: PartialEq, Balance: MathBalance> Sell<AssetId, Balance> {
pub fn is_valid(&self) -> bool {
self.take.is_valid()
}
pub fn new(
base: AssetId,
quote: AssetId,
base_amount: Balance,
minimal_base_unit_price_in_quote: Balance,
minimal_base_unit_price_in_quote: Ratio,
) -> Self {
Self {
take: Take { amount: base_amount, limit: minimal_base_unit_price_in_quote },
Expand All @@ -70,9 +75,11 @@ impl<AssetId: PartialEq, Balance: PartialOrd + Zero + SafeArithmetic> Sell<Asset
pub struct CurrencyPair<AssetId> {
/// See [Base Currency](https://www.investopedia.com/terms/b/basecurrency.asp).
/// Also can be named `native`(to the market) currency.
/// Usually less stable, can be used as collateral.
pub base: AssetId,
/// Counter currency.
/// Also can be named `price` currency.
/// Usually more stable, may be `borrowable` asset.
pub quote: AssetId,
}

Expand All @@ -90,9 +97,10 @@ impl<AssetId: PartialEq> CurrencyPair<AssetId> {
/// assert_eq!(slice[0], pair.base);
/// assert_eq!(slice[1], pair.quote);
/// ```
/// ```compile_fail
/// ```rust
/// # let pair = composable_traits::defi::CurrencyPair::<u128>::new(13, 42);
/// # let slice = pair.as_slice();
/// // it is copy
/// drop(pair);
/// let _ = slice[0];
/// ```
Expand Down Expand Up @@ -185,6 +193,7 @@ pub trait DeFiComposableConfig: frame_system::Config {
type MayBeAssetId: AssetIdLike + MaybeSerializeDeserialize + Default;

type Balance: BalanceLike
+ MathBalance
+ Default
+ Parameter
+ Codec
Expand All @@ -197,12 +206,52 @@ pub trait DeFiComposableConfig: frame_system::Config {
+ From<u64> // at least 64 bit
+ Zero
+ FixedPointOperand
+ Into<LiftedFixedBalance> // integer part not more than bits in this
+ Into<u128>; // cannot do From<u128>, until LiftedFixedBalance integer part is larger than 128
// bit
}

/// The fixed point number from 0..to max.
/// Unlike `Ratio` it can be more than 1.
/// And unlike `NormalizedCollateralFactor`, it can be less than one.
/// The fixed point number from 0..to max
pub type Rate = FixedU128;

/// Is [1..MAX]
pub type OneOrMoreFixedU128 = FixedU128;

/// The fixed point number of suggested by substrate precision
/// Must be (1.0..MAX] because applied only to price normalized values
pub type MoreThanOneFixedU128 = FixedU128;

/// Must be [0..1]
pub type ZeroToOneFixedU128 = FixedU128;

/// Number like of higher bits, so that amount and balance calculations are done it it with higher
/// precision via fixed point.
/// While this is 128 bit, cannot support u128 because 18 bits are for of mantissa (so maximal
/// integer is 110 bit). Can support u128 if lift upper to use FixedU256 analog.
pub type LiftedFixedBalance = FixedU128;

/// unitless ratio of one thing to other.
pub type Ratio = FixedU128;

#[cfg(test)]
mod tests {
use super::{Ratio, Take};
use sp_runtime::FixedPointNumber;

#[test]
fn take_ratio_half() {
let price = 10;
let amount = 100_u128;
let take = Take::new(amount, Ratio::saturating_from_integer(price));
let result = take.quote_amount(amount / 2).unwrap();
assert_eq!(result, price * amount / 2);
}

#[test]
fn take_ratio_half_amount_half_price() {
let price_part = 50;
let amount = 100_u128;
let take = Take::new(amount, Ratio::saturating_from_rational(price_part, 100));
let result = take.quote_amount(amount).unwrap();
assert_eq!(result, price_part * amount / 100);
}
}
47 changes: 21 additions & 26 deletions frame/composable-traits/src/lending/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,11 @@ use sp_runtime::{
use sp_arithmetic::per_things::Percent;

use crate::{
defi::Rate,
loans::{DurationSeconds, ONE_HOUR},
math::{LiftedFixedBalance, SafeArithmetic},
defi::{LiftedFixedBalance, Rate, ZeroToOneFixedU128},
math::SafeArithmetic,
time::{DurationSeconds, SECONDS_PER_YEAR_NAIVE},
};

/// The fixed point number of suggested by substrate precision
/// Must be (1.0.. because applied only to price normalized values
pub type NormalizedCollateralFactor = FixedU128;

/// Must be [0..1]
/// TODO: implement Ratio as wrapper over FixedU128
pub type Ratio = FixedU128;

/// current notion of year will take away 1/365 from lenders and give away to borrowers (as does no
/// accounts to length of year)
pub const SECONDS_PER_YEAR: DurationSeconds = 365 * 24 * ONE_HOUR;

/// utilization_ratio = total_borrows / (total_cash + total_borrows)
pub fn calc_utilization_ratio(
cash: LiftedFixedBalance,
Expand Down Expand Up @@ -110,9 +98,13 @@ impl InterestRateModel {
}

/// Calculates the current supply interest rate
pub fn get_supply_rate(borrow_rate: Rate, util: Ratio, reserve_factor: Ratio) -> Rate {
pub fn get_supply_rate(
borrow_rate: Rate,
util: ZeroToOneFixedU128,
reserve_factor: ZeroToOneFixedU128,
) -> Rate {
// ((1 - reserve_factor) * borrow_rate) * utilization
let one_minus_reserve_factor = Ratio::one().saturating_sub(reserve_factor);
let one_minus_reserve_factor = ZeroToOneFixedU128::one().saturating_sub(reserve_factor);
let rate_to_pool = borrow_rate.saturating_mul(one_minus_reserve_factor);

rate_to_pool.saturating_mul(util)
Expand Down Expand Up @@ -151,15 +143,18 @@ pub struct JumpModel {
}

impl JumpModel {
pub const MAX_BASE_RATE: Ratio = Ratio::from_inner(100_000_000_000_000_000); // 10%
pub const MAX_JUMP_RATE: Ratio = Ratio::from_inner(300_000_000_000_000_000); // 30%
pub const MAX_FULL_RATE: Ratio = Ratio::from_inner(500_000_000_000_000_000); // 50%
pub const MAX_BASE_RATE: ZeroToOneFixedU128 =
ZeroToOneFixedU128::from_inner(ZeroToOneFixedU128::DIV * 10 / 100);
pub const MAX_JUMP_RATE: ZeroToOneFixedU128 =
ZeroToOneFixedU128::from_inner(ZeroToOneFixedU128::DIV * 30 / 100);
pub const MAX_FULL_RATE: ZeroToOneFixedU128 =
ZeroToOneFixedU128::from_inner(ZeroToOneFixedU128::DIV * 50 / 100);

/// Create a new rate model
pub fn new(
base_rate: Ratio,
jump_rate: Ratio,
full_rate: Ratio,
base_rate: ZeroToOneFixedU128,
jump_rate: ZeroToOneFixedU128,
full_rate: ZeroToOneFixedU128,
target_utilization: Percent,
) -> Option<JumpModel> {
let model = Self { base_rate, jump_rate, full_rate, target_utilization };
Expand Down Expand Up @@ -390,7 +385,7 @@ pub fn accrued_interest(
borrow_rate
.checked_mul_int(amount)?
.checked_mul(delta_time.into())?
.checked_div(SECONDS_PER_YEAR.into())
.checked_div(SECONDS_PER_YEAR_NAIVE.into())
}

/// compounding increment of borrow index
Expand All @@ -402,7 +397,7 @@ pub fn increment_index(
borrow_rate
.safe_mul(&index)?
.safe_mul(&FixedU128::saturating_from_integer(delta_time))?
.safe_div(&FixedU128::saturating_from_integer(SECONDS_PER_YEAR))?
.safe_div(&FixedU128::saturating_from_integer(SECONDS_PER_YEAR_NAIVE))?
.safe_add(&index)
}

Expand All @@ -412,5 +407,5 @@ pub fn increment_borrow_rate(
) -> Result<Rate, ArithmeticError> {
borrow_rate
.safe_mul(&FixedU128::saturating_from_integer(delta_time))?
.safe_div(&FixedU128::saturating_from_integer(SECONDS_PER_YEAR))
.safe_div(&FixedU128::saturating_from_integer(SECONDS_PER_YEAR_NAIVE))
}
Loading