Module levana_perpswap_cosmos::number
source · Expand description
Number type and helpers Provides a number of data types, methods, and traits to have more fine-grained and strongly-typed control of numeric data.
§Base, quote, notional, and collateral
In general markets, a currency
pair like BTC/USD
consists of a base currency (BTC
) and a quote currency (USD
). In our
platform, we talk about the notional and collateral assets, where the
collateral asset is what gets deposited in the platform and notional is
(generally) the risk asset being speculated on.
Generally speaking, in most perpetual swaps platforms, the base and notional assets are the same, and the quote and collateral assets are the same. However, our platform supports a concept called crypto denominated pairs. In these, we use the base/risk asset as collateral and quote is the notional. This causes a cascade of changes around leverage and price management.
However, all those changes are internal to the protocol. The user-facing API should almost exclusively care about base and quote (besides the fact that the user will interact with the contracts by depositing and withdrawing collateral assets). The purpose of this module is to provide data types that provide safety and clarity around whether we’re dealing with the base/quote view of the world (user-facing) or notional/collateral (internal) view.
§Decimal256, NonZero, Signed, and UnsignedDecimal
Math generally uses Decimal256. However, this type alone cannot express negative numbers, and we often want to ensure additional constraints at compile time.
A combination of traits and newtype wrappers gives us a robust framework:
-
UnsignedDecimal
: a trait, not a concrete type, which is implemented forCollateral
,Notional
, and several other numeric types. -
NonZero<T>
: a newtype wrapper which ensures that the value is not zero. It’s generally used for types whereT: UnsignedDecimal
. -
Signed<T>
: a newtype wrapper which allows for positive or negative values. It’s also generally used for types whereT: UnsignedDecimal
.
Putting it all together, here are some examples. Note that these are merely illustrative. Real-world problems would require a price point to convert between Collateral and Notional:
§UnsignedDecimal
Collateral
implements UnsignedDecimal
, and so we can add two Collateral
values together via .checked_add()
.
However, we cannot add a Collateral
and some other Decimal256
. Instead
we need to call .into_decimal256()
, do our math with another Decimal256
,
and then convert it to any T: UnsignedDecimal
via T::from_decimal256()
.
example
use levana_perpswap_cosmos::number::*;
use cosmwasm_std::Decimal256;
use std::str::FromStr;
let lhs:Collateral = "1.23".parse().unwrap();
let rhs:Decimal256 = "4.56".parse().unwrap();
let decimal_result = lhs.into_decimal256().checked_add(rhs).unwrap();
let output:Notional = Notional::from_decimal256(decimal_result);
§NonZero
NonZero<Collateral>
allows us to call various .checked_*
math methods
with another NonZero<Collateral>
.
However, if we want to do math with a different underlying type - we do need to drop down to that common type. There’s two approaches (both of which return an Option, in case the resulting value is zero):
- If the inner NonZero type stays the same (i.e. it’s all
Collateral
) then call.raw()
to get the inner type, do your math, and then convert back to the NonZero wrapper viaNonZero::new()
- If you need a
Decimal256
, then call.into_decimal256()
to get the underlyingDecimal256
type, do your math, and then convert back toNonZero<T>
viaNonZero::new(T::from_decimal256(value))
. This is usually the case when the type ofT
has changed (i.e. fromCollateral
toNotional
)
example 1
use levana_perpswap_cosmos::number::*;
use cosmwasm_std::Decimal256;
use std::str::FromStr;
let lhs:NonZero<Collateral> = "1.23".parse().unwrap();
let rhs:Collateral = "4.56".parse().unwrap();
let collateral_result = lhs.raw().checked_add(rhs).unwrap();
let output:NonZero<Collateral> = NonZero::new(collateral_result).unwrap();
example 2
use levana_perpswap_cosmos::number::*;
use cosmwasm_std::Decimal256;
use std::str::FromStr;
let lhs:NonZero<Collateral> = "1.23".parse().unwrap();
let rhs:Decimal256 = "4.56".parse().unwrap();
let decimal_result = lhs.into_decimal256().checked_add(rhs).unwrap();
let notional_result = Notional::from_decimal256(decimal_result);
let output:NonZero<Notional> = NonZero::new(notional_result).unwrap();
§Signed
Signed<Collateral>
also allows us to call various .checked_*
math methods
when the inner type is the same. However, there are some differences when
comparing to the NonZero
methods:
-
To get the underlying
T
, call.abs_unsigned()
instead of.raw()
. The sign is now lost, it’s not a pure raw conversion. -
To get back from the underlying
T
, callT::into_signed()
-
There is no direct conversion to
Decimal256
. -
There are helpers for the ubiquitous use-case of
Signed<Decimal256>
This is such a common occurance, it has its own type alias:Number
.
example 1
use levana_perpswap_cosmos::number::*;
use cosmwasm_std::Decimal256;
use std::str::FromStr;
let lhs:Signed<Collateral> = "-1.23".parse().unwrap();
let rhs:Decimal256 = "4.56".parse().unwrap();
let decimal_result = lhs.abs_unsigned().into_decimal256().checked_mul(rhs).unwrap();
let notional_result = Notional::from_decimal256(decimal_result);
// bring back our negative sign
let output:Signed<Notional> = -notional_result.into_signed();
example 2
use levana_perpswap_cosmos::number::*;
use cosmwasm_std::Decimal256;
use std::str::FromStr;
let lhs:Signed<Collateral> = "-1.23".parse().unwrap();
let rhs:Number = "4.56".parse().unwrap();
let number_result = lhs.into_number().checked_mul(rhs).unwrap();
let output:Signed<Notional> = Signed::<Notional>::from_number(number_result);
Modules§
- Provides specialized types that define a ratio that must be within a specific range. This can be helpful when defining an interface that contains a ratio represented by a decimal but the ratio is logically constrained by specific bounds.
Structs§
- Unsigned value
- Unsigned value
- Unsigned value
- Unsigned value
- Unsigned value
- Unsigned value
- Ensure that the inner value is never 0.
- Unsigned value
- Unsigned value
- Wrap up any UnsignedDecimal to provide negative values too.
- Unsigned value
Traits§
- Generalizes any newtype wrapper around a Decimal256.
Type Aliases§
- A signed number type with high fidelity.
- A special case of NonZero which stores a big endian array of data.