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

feat: add wrappers for dynamically rebased tokens #935

Closed
wants to merge 1 commit into from
Closed
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
14 changes: 10 additions & 4 deletions crates/oracle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,20 @@ pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "

# Parachain dependencies
security = { path = "../security", default-features = false }
primitives = { package = "interbtc-primitives", path = "../../primitives", default-features = false }
staking = { path = "../staking", default-features = false }
currency = { path = "../currency", default-features = false }
traits = { path = '../traits', default-features = false }

[dev-dependencies]
mocktopus = "0.8.0"
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
primitives = { package = "interbtc-primitives", path = "../../primitives", default-features = false }

# Orml dependencies
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "dc39cfddefb10ef0de23655e2c3dcdab66a19404", default-features = false }
orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "dc39cfddefb10ef0de23655e2c3dcdab66a19404", default-features = false }

[dev-dependencies]
mocktopus = "0.8.0"
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }

[features]
default = ["std"]
std = [
Expand All @@ -54,7 +55,12 @@ std = [
"security/std",
"staking/std",
"currency/std",
"traits/std",

"primitives/std",

"orml-tokens/std",
"orml-traits/std",
]
runtime-benchmarks = [
"frame-benchmarking",
Expand Down
4 changes: 4 additions & 0 deletions crates/oracle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ use traits::OracleApi;
pub use pallet::*;
pub use primitives::{oracle::Key as OracleKey, CurrencyId, TruncateFixedPointToInt};
pub use traits::OnExchangeRateChange;
pub use types::*;

#[derive(Encode, Decode, Eq, PartialEq, Clone, Copy, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
pub struct TimestampedValue<Value, Moment> {
Expand Down Expand Up @@ -146,6 +147,9 @@ pub mod pallet {
#[pallet::getter(fn authorized_oracles)]
pub type AuthorizedOracles<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, Vec<u8>, ValueQuery>;

#[pallet::storage]
pub type RebaseTokens<T: Config> = StorageMap<_, Blake2_128Concat, CurrencyId, CurrencyId, OptionQuery>;

#[pallet::type_value]
pub(super) fn DefaultForStorageVersion() -> Version {
Version::V0
Expand Down
10 changes: 8 additions & 2 deletions crates/oracle/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
use crate as oracle;
use crate::{Config, Error};
use crate::{types::*, Config, Error};
use frame_support::{
parameter_types,
traits::{ConstU32, Everything, GenesisBuild},
};
use mocktopus::mocking::clear_mocks;
use orml_traits::parameter_type_with_key;
pub use primitives::{CurrencyId::Token, TokenSymbol::*};
pub use primitives::{
CurrencyId::{ForeignAsset, Token},
TokenSymbol::*,
};
use sp_arithmetic::{FixedI128, FixedU128};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};

pub type OracleRebase =
Combiner<AccountId, IsRebaseToken<Test>, Mapper<AccountId, RebaseAdapter<Test>, Tokens>, Tokens>;

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;

Expand Down
43 changes: 42 additions & 1 deletion crates/oracle/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{mock::*, CurrencyId, OracleKey};
use crate::{mock::*, CurrencyId, OracleKey, RebaseTokens};
use frame_support::{assert_err, assert_ok, dispatch::DispatchError};
use mocktopus::mocking::*;
use orml_traits::MultiCurrency;
use sp_arithmetic::FixedU128;
use sp_runtime::FixedPointNumber;

Expand Down Expand Up @@ -363,3 +364,43 @@ fn test_median() {
assert_eq!(Oracle::median(input_fixedpoint), output_fixedpoint);
}
}

#[test]
fn test_rebase_tokens() {
run_test(|| {
Oracle::is_authorized.mock_safe(|_| MockResult::Return(true));
RebaseTokens::<Test>::insert(ForeignAsset(2), Token(KSM));
assert_ok!(Oracle::feed_values(
RuntimeOrigin::signed(0),
vec![
// LKSM/BTC => 0.00018807
(
OracleKey::ExchangeRate(ForeignAsset(2)),
FixedU128::from_inner(53_171_691_391_503_161_727_385_600)
),
// KSM/BTC => 0.00148502
(
OracleKey::ExchangeRate(Token(KSM)),
FixedU128::from_inner(6_733_916_041_534_794_629_120_000)
),
],
));

mine_block();

assert_ok!(Tokens::set_balance(
RuntimeOrigin::root(),
0,
ForeignAsset(2),
1000000000000,
0
));

// LKSM/KSM => 0.12664475899314487
assert_eq!(126644758993, OracleRebase::total_issuance(ForeignAsset(2)));
assert_eq!(126644758993, OracleRebase::total_balance(ForeignAsset(2), &0));
assert_eq!(126644758993, OracleRebase::free_balance(ForeignAsset(2), &0));

assert_ok!(OracleRebase::transfer(ForeignAsset(2), &0, &1, 126644758993));
});
}
201 changes: 201 additions & 0 deletions crates/oracle/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use crate::{Config, CurrencyId, Pallet, RebaseTokens};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::traits::Contains;
use orml_tokens::ConvertBalance;
use orml_traits::MultiCurrency;
use scale_info::TypeInfo;
use sp_runtime::DispatchResult;
use sp_std::marker::PhantomData;

pub(crate) type BalanceOf<T> = <T as currency::Config>::Balance;

Expand All @@ -11,3 +17,198 @@ pub enum Version {
/// Initial version.
V0,
}

pub struct RebaseAdapter<T>(PhantomData<T>);

impl<T> ConvertBalance<BalanceOf<T>, BalanceOf<T>> for RebaseAdapter<T>
where
T: Config,
{
type AssetId = CurrencyId;

fn convert_balance(amount: BalanceOf<T>, from_asset_id: CurrencyId) -> BalanceOf<T> {
if let Some(to_asset_id) = RebaseTokens::<T>::get(&from_asset_id) {
let amount = Pallet::<T>::collateral_to_wrapped(amount, from_asset_id).unwrap_or_default();
Pallet::<T>::wrapped_to_collateral(amount, to_asset_id).unwrap_or_default()
} else {
amount
}
}

fn convert_balance_back(amount: BalanceOf<T>, from_asset_id: CurrencyId) -> BalanceOf<T> {
if let Some(to_asset_id) = RebaseTokens::<T>::get(&from_asset_id) {
let amount = Pallet::<T>::collateral_to_wrapped(amount, to_asset_id).unwrap_or_default();
Pallet::<T>::wrapped_to_collateral(amount, from_asset_id).unwrap_or_default()
} else {
amount
}
}
}

pub struct IsRebaseToken<T>(PhantomData<T>);

impl<T> Contains<CurrencyId> for IsRebaseToken<T>
where
T: Config,
{
fn contains(currency_id: &CurrencyId) -> bool {
RebaseTokens::<T>::contains_key(currency_id)
}
}

pub struct Combiner<AccountId, TestKey, A, B>(PhantomData<(AccountId, TestKey, A, B)>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add some docstrings to each of these structs explaining what they do?


impl<AccountId, TestKey, A, B> MultiCurrency<AccountId> for Combiner<AccountId, TestKey, A, B>
where
TestKey: Contains<CurrencyId>,
A: MultiCurrency<AccountId, CurrencyId = CurrencyId, Balance = <B as MultiCurrency<AccountId>>::Balance>,
B: MultiCurrency<AccountId, CurrencyId = CurrencyId>,
{
type CurrencyId = CurrencyId;
type Balance = <B as MultiCurrency<AccountId>>::Balance;

fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance {
if TestKey::contains(&currency_id) {
A::minimum_balance(currency_id)
} else {
B::minimum_balance(currency_id)
}
}

fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance {
if TestKey::contains(&currency_id) {
A::total_issuance(currency_id)
} else {
B::total_issuance(currency_id)
}
}

fn total_balance(currency_id: Self::CurrencyId, who: &AccountId) -> Self::Balance {
if TestKey::contains(&currency_id) {
A::total_balance(currency_id, who)
} else {
B::total_balance(currency_id, who)
}
}

fn free_balance(currency_id: Self::CurrencyId, who: &AccountId) -> Self::Balance {
if TestKey::contains(&currency_id) {
A::free_balance(currency_id, who)
} else {
B::free_balance(currency_id, who)
}
}

fn ensure_can_withdraw(currency_id: Self::CurrencyId, who: &AccountId, amount: Self::Balance) -> DispatchResult {
if TestKey::contains(&currency_id) {
A::ensure_can_withdraw(currency_id, who, amount)
} else {
B::ensure_can_withdraw(currency_id, who, amount)
}
}

fn transfer(
currency_id: Self::CurrencyId,
from: &AccountId,
to: &AccountId,
amount: Self::Balance,
) -> DispatchResult {
if TestKey::contains(&currency_id) {
A::transfer(currency_id, from, to, amount)
} else {
B::transfer(currency_id, from, to, amount)
}
}

fn deposit(currency_id: Self::CurrencyId, who: &AccountId, amount: Self::Balance) -> DispatchResult {
if TestKey::contains(&currency_id) {
A::deposit(currency_id, who, amount)
} else {
B::deposit(currency_id, who, amount)
}
}

fn withdraw(currency_id: Self::CurrencyId, who: &AccountId, amount: Self::Balance) -> DispatchResult {
if TestKey::contains(&currency_id) {
A::withdraw(currency_id, who, amount)
} else {
B::withdraw(currency_id, who, amount)
}
}

fn can_slash(currency_id: Self::CurrencyId, who: &AccountId, value: Self::Balance) -> bool {
if TestKey::contains(&currency_id) {
A::can_slash(currency_id, who, value)
} else {
B::can_slash(currency_id, who, value)
}
}

fn slash(currency_id: Self::CurrencyId, who: &AccountId, amount: Self::Balance) -> Self::Balance {
if TestKey::contains(&currency_id) {
A::slash(currency_id, who, amount)
} else {
B::slash(currency_id, who, amount)
}
}
}

pub struct Mapper<AccountId, C, T>(PhantomData<(AccountId, C, T)>);

impl<AccountId, C, T> MultiCurrency<AccountId> for Mapper<AccountId, C, T>
where
C: ConvertBalance<
<T as MultiCurrency<AccountId>>::Balance,
<T as MultiCurrency<AccountId>>::Balance,
AssetId = CurrencyId,
>,
T: MultiCurrency<AccountId, CurrencyId = CurrencyId>,
{
type CurrencyId = CurrencyId;
type Balance = <T as MultiCurrency<AccountId>>::Balance;

fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance {
C::convert_balance(T::minimum_balance(currency_id), currency_id)
}

fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance {
C::convert_balance(T::total_issuance(currency_id), currency_id)
}

fn total_balance(currency_id: Self::CurrencyId, who: &AccountId) -> Self::Balance {
C::convert_balance(T::total_balance(currency_id, who), currency_id)
}

fn free_balance(currency_id: Self::CurrencyId, who: &AccountId) -> Self::Balance {
C::convert_balance(T::free_balance(currency_id, who), currency_id)
}

fn ensure_can_withdraw(currency_id: Self::CurrencyId, who: &AccountId, amount: Self::Balance) -> DispatchResult {
T::ensure_can_withdraw(currency_id, who, C::convert_balance_back(amount, currency_id))
}

fn transfer(
currency_id: Self::CurrencyId,
from: &AccountId,
to: &AccountId,
amount: Self::Balance,
) -> DispatchResult {
T::transfer(currency_id, from, to, C::convert_balance_back(amount, currency_id))
}

fn deposit(currency_id: Self::CurrencyId, who: &AccountId, amount: Self::Balance) -> DispatchResult {
T::deposit(currency_id, who, C::convert_balance_back(amount, currency_id))
}

fn withdraw(currency_id: Self::CurrencyId, who: &AccountId, amount: Self::Balance) -> DispatchResult {
T::withdraw(currency_id, who, C::convert_balance_back(amount, currency_id))
}

fn can_slash(currency_id: Self::CurrencyId, who: &AccountId, value: Self::Balance) -> bool {
T::can_slash(currency_id, who, C::convert_balance_back(value, currency_id))
}

fn slash(currency_id: Self::CurrencyId, who: &AccountId, amount: Self::Balance) -> Self::Balance {
T::slash(currency_id, who, C::convert_balance_back(amount, currency_id))
}
}