diff --git a/Cargo.lock b/Cargo.lock index c93306162..e0f48fff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3782,14 +3782,26 @@ dependencies = [ [[package]] name = "hydradx-adapters" -version = "0.3.0" +version = "0.4.0" dependencies = [ "frame-support", + "frame-system", + "hydra-dx-math", "hydradx-traits", "lazy_static", "log", + "orml-traits", + "orml-xcm-support", + "pallet-circuit-breaker", + "pallet-dynamic-fees", + "pallet-ema-oracle", + "pallet-liquidity-mining", + "pallet-omnipool", + "pallet-omnipool-liquidity-mining", "pallet-transaction-multi-payment", "parity-scale-codec", + "primitive-types", + "primitives", "sp-runtime", "sp-std", "xcm", @@ -3799,7 +3811,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "164.0.0" +version = "165.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -9303,7 +9315,7 @@ dependencies = [ [[package]] name = "primitives" -version = "5.8.0" +version = "5.8.1" dependencies = [ "frame-support", "hex-literal 0.3.4", @@ -10055,7 +10067,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.7.18" +version = "1.7.19" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -10075,6 +10087,7 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "hex-literal 0.4.1", + "hydradx-adapters", "hydradx-runtime", "hydradx-traits", "orml-tokens", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 9a4d94a94..4a3474237 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.7.18" +version = "1.7.19" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" @@ -14,6 +14,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread"] } scraper = { workspace = true } primitives = { workspace = true } hydradx-runtime = { workspace = true } +hydradx-adapters = { workspace = true } pallet-omnipool = { workspace = true } pallet-circuit-breaker = { workspace = true } pallet-omnipool-liquidity-mining = { workspace = true } diff --git a/integration-tests/src/omnipool_price_provider.rs b/integration-tests/src/omnipool_price_provider.rs index 4aece5dc7..3ca0a11a9 100644 --- a/integration-tests/src/omnipool_price_provider.rs +++ b/integration-tests/src/omnipool_price_provider.rs @@ -4,7 +4,8 @@ use crate::polkadot_test_net::*; use frame_support::assert_ok; use frame_system::RawOrigin; -use hydradx_runtime::{adapters::OraclePriceProviderAdapterForOmnipool, Omnipool, RuntimeOrigin, Tokens}; +use hydradx_adapters::OraclePriceProviderAdapterForOmnipool; +use hydradx_runtime::{Omnipool, RuntimeOrigin, Tokens}; use hydradx_traits::{OraclePeriod, PriceOracle}; use primitives::{AssetId, Balance}; use sp_runtime::{FixedU128, Permill}; diff --git a/integration-tests/src/oracle.rs b/integration-tests/src/oracle.rs index 60e9d3621..12d36a8e9 100644 --- a/integration-tests/src/oracle.rs +++ b/integration-tests/src/oracle.rs @@ -7,13 +7,14 @@ use frame_support::{ sp_runtime::{FixedU128, Permill}, traits::{tokens::fungibles::Mutate, OnFinalize, OnInitialize}, }; -use hydradx_runtime::{EmaOracle, RuntimeOrigin, OMNIPOOL_SOURCE}; +use hydradx_runtime::{EmaOracle, RuntimeOrigin}; use hydradx_traits::{ AggregatedPriceOracle, OraclePeriod::{self, *}, }; use pallet_ema_oracle::OracleError; use polkadot_primitives::v2::BlockNumber; +use primitives::constants::chain::OMNIPOOL_SOURCE; use xcm_emulator::TestExt; pub fn hydradx_run_to_block(to: BlockNumber) { diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index db67695c1..b68cf1f97 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "primitives" -version = "5.8.0" +version = "5.8.1" authors = ["GalacticCouncil"] edition = "2021" repository = "https://github.com/galacticcouncil/HydraDX-node" diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index ce89b5bbe..c0e8cbb88 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -81,6 +81,9 @@ pub mod chain { WEIGHT_REF_TIME_PER_SECOND.saturating_div(2), polkadot_primitives::v2::MAX_POV_SIZE as u64, ); + + /// The source of the data for the oracle. + pub const OMNIPOOL_SOURCE: [u8; 8] = *b"omnipool"; } #[cfg(test)] diff --git a/runtime/adapters/Cargo.toml b/runtime/adapters/Cargo.toml index c44279cbd..1efea0a61 100644 --- a/runtime/adapters/Cargo.toml +++ b/runtime/adapters/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-adapters" -version = "0.3.0" +version = "0.4.0" description = "Structs and other generic types for building runtimes." authors = ["GalacticCouncil"] edition = "2021" @@ -12,19 +12,33 @@ codec = { default-features = false, features = ["derive"], package = "parity-sca log = { version = "0.4.17", default-features = false } # HydraDX dependencies +primitives = { workspace = true } hydradx-traits = { workspace = true } +hydra-dx-math = { workspace = true } pallet-transaction-multi-payment = { workspace = true } +pallet-omnipool = { workspace = true } +pallet-ema-oracle = { workspace = true } +pallet-circuit-breaker = { workspace = true } +warehouse-liquidity-mining = { workspace = true } +pallet-omnipool-liquidity-mining = { workspace = true } +pallet-dynamic-fees = { workspace = true } # Substrate dependencies frame-support = { workspace = true } +frame-system = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } +primitive-types = { workspace = true } # Polkadot dependencies polkadot-xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } +# ORML dependencies +orml-xcm-support = { workspace = true } +orml-traits = { workspace = true } + [dev-dependencies] lazy_static = { features = ["spin_no_std"], version = "1.4.0" } diff --git a/runtime/adapters/src/lib.rs b/runtime/adapters/src/lib.rs index 760cdef3f..aa2b089f9 100644 --- a/runtime/adapters/src/lib.rs +++ b/runtime/adapters/src/lib.rs @@ -17,18 +17,39 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::weights::{Weight, WeightToFee}; -use hydradx_traits::NativePriceOracle; +use codec::FullCodec; +use frame_support::{ + sp_runtime::{ + traits::{AtLeast32BitUnsigned, Convert, Get, MaybeSerializeDeserialize, Saturating, Zero}, + ArithmeticError, DispatchError, FixedPointNumber, FixedPointOperand, FixedU128, SaturatedConversion, + }, + traits::Contains, + weights::{Weight, WeightToFee}, +}; +use hydra_dx_math::{ + ema::EmaPrice, + omnipool::types::BalanceUpdate, + support::rational::{round_to_rational, Rounding}, +}; +use hydradx_traits::{ + liquidity_mining::PriceAdjustment, AggregatedOracle, AggregatedPriceOracle, NativePriceOracle, + OnLiquidityChangedHandler, OnTradeHandler, OraclePeriod, PriceOracle, +}; +use orml_xcm_support::{OnDepositFail, UnknownAsset as UnknownAssetT}; +use pallet_circuit_breaker::WeightInfo; +use pallet_ema_oracle::{OnActivityHandler, OracleError, Price}; +use pallet_omnipool::traits::{AssetInfo, ExternalPriceProvider, OmnipoolHooks}; use pallet_transaction_multi_payment::DepositFee; use polkadot_xcm::latest::prelude::*; -use sp_runtime::traits::Get; -use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Convert, Saturating, Zero}, - FixedPointNumber, FixedPointOperand, SaturatedConversion, -}; -use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData}; +use primitive_types::U128; +use primitives::{constants::chain::OMNIPOOL_SOURCE, AssetId, Balance, BlockNumber}; +use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, marker::PhantomData}; +use warehouse_liquidity_mining::GlobalFarmData; use xcm_builder::TakeRevenue; -use xcm_executor::{traits::WeightTrader, Assets}; +use xcm_executor::{ + traits::{Convert as MoreConvert, MatchesFungible, TransactAsset, WeightTrader}, + Assets, +}; pub mod inspect; @@ -219,3 +240,408 @@ impl< } } } + +/// Passes on trade and liquidity data from the omnipool to the oracle. +pub struct OmnipoolHookAdapter(PhantomData<(Origin, Lrna, Runtime)>); + +impl OmnipoolHooks for OmnipoolHookAdapter +where + Lrna: Get, + Runtime: pallet_ema_oracle::Config + pallet_circuit_breaker::Config + frame_system::Config, +{ + type Error = DispatchError; + + fn on_liquidity_changed(origin: Origin, asset: AssetInfo) -> Result { + OnActivityHandler::::on_liquidity_changed( + OMNIPOOL_SOURCE, + asset.asset_id, + Lrna::get(), + *asset.delta_changes.delta_reserve, + *asset.delta_changes.delta_hub_reserve, + asset.after.reserve, + asset.after.hub_reserve, + ) + .map_err(|(_, e)| e)?; + + match asset.delta_changes.delta_reserve { + BalanceUpdate::Increase(amount) => pallet_circuit_breaker::Pallet::::ensure_add_liquidity_limit( + origin, + asset.asset_id.into(), + asset.before.reserve.into(), + amount.into(), + )?, + BalanceUpdate::Decrease(amount) => { + pallet_circuit_breaker::Pallet::::ensure_remove_liquidity_limit( + origin, + asset.asset_id.into(), + asset.before.reserve.into(), + amount.into(), + )? + } + }; + + Ok(Self::on_liquidity_changed_weight()) + } + + fn on_trade( + _origin: Origin, + asset_in: AssetInfo, + asset_out: AssetInfo, + ) -> Result { + OnActivityHandler::::on_trade( + OMNIPOOL_SOURCE, + asset_in.asset_id, + Lrna::get(), + *asset_in.delta_changes.delta_reserve, + *asset_in.delta_changes.delta_hub_reserve, + asset_in.after.reserve, + asset_in.after.hub_reserve, + ) + .map_err(|(_, e)| e)?; + + OnActivityHandler::::on_trade( + OMNIPOOL_SOURCE, + Lrna::get(), + asset_out.asset_id, + *asset_out.delta_changes.delta_hub_reserve, + *asset_out.delta_changes.delta_reserve, + asset_out.after.hub_reserve, + asset_out.after.reserve, + ) + .map_err(|(_, e)| e)?; + + let amount_in = *asset_in.delta_changes.delta_reserve; + let amount_out = *asset_out.delta_changes.delta_reserve; + + pallet_circuit_breaker::Pallet::::ensure_pool_state_change_limit( + asset_in.asset_id.into(), + asset_in.before.reserve.into(), + amount_in.into(), + asset_out.asset_id.into(), + asset_out.before.reserve.into(), + amount_out.into(), + )?; + + Ok(Self::on_trade_weight()) + } + + fn on_hub_asset_trade(_origin: Origin, asset: AssetInfo) -> Result { + OnActivityHandler::::on_trade( + OMNIPOOL_SOURCE, + Lrna::get(), + asset.asset_id, + *asset.delta_changes.delta_hub_reserve, + *asset.delta_changes.delta_reserve, + asset.after.hub_reserve, + asset.after.reserve, + ) + .map_err(|(_, e)| e)?; + + let amount_out = *asset.delta_changes.delta_reserve; + + pallet_circuit_breaker::Pallet::::ensure_pool_state_change_limit( + Lrna::get().into(), + Balance::zero().into(), + Balance::zero().into(), + asset.asset_id.into(), + asset.before.reserve.into(), + amount_out.into(), + )?; + + Ok(Self::on_trade_weight()) + } + + fn on_liquidity_changed_weight() -> Weight { + let w1 = OnActivityHandler::::on_liquidity_changed_weight(); + let w2 = ::WeightInfo::ensure_add_liquidity_limit() + .max(::WeightInfo::ensure_remove_liquidity_limit()); + let w3 = ::WeightInfo::on_finalize_single_liquidity_limit_entry(); + w1.saturating_add(w2).saturating_add(w3) + } + + fn on_trade_weight() -> Weight { + let w1 = OnActivityHandler::::on_trade_weight().saturating_mul(2); + let w2 = ::WeightInfo::ensure_pool_state_change_limit(); + let w3 = ::WeightInfo::on_finalize_single_trade_limit_entry(); + w1.saturating_add(w2).saturating_add(w3) + } +} + +/// Passes ema oracle price to the omnipool. +pub struct EmaOraclePriceAdapter(PhantomData<(Period, Runtime)>); + +impl ExternalPriceProvider for EmaOraclePriceAdapter +where + Period: Get, + Runtime: pallet_ema_oracle::Config + pallet_omnipool::Config, +{ + type Error = DispatchError; + + fn get_price(asset_a: AssetId, asset_b: AssetId) -> Result { + let (price, _) = + pallet_ema_oracle::Pallet::::get_price(asset_a, asset_b, Period::get(), OMNIPOOL_SOURCE) + .map_err(|_| pallet_omnipool::Error::::PriceDifferenceTooHigh)?; + Ok(price) + } + + fn get_price_weight() -> Weight { + pallet_ema_oracle::Pallet::::get_price_weight() + } +} + +pub struct OraclePriceProviderAdapterForOmnipool( + PhantomData<(AssetId, AggregatedPriceGetter, Lrna)>, +); + +impl PriceOracle + for OraclePriceProviderAdapterForOmnipool +where + u32: From, + AggregatedPriceGetter: AggregatedPriceOracle, + Lrna: Get, +{ + type Price = EmaPrice; + + fn price(asset_a: AssetId, asset_b: AssetId, period: OraclePeriod) -> Option { + let price_asset_a_lrna = AggregatedPriceGetter::get_price(asset_a, Lrna::get(), period, OMNIPOOL_SOURCE); + + let price_asset_a_lrna = match price_asset_a_lrna { + Ok(price) => price.0, + Err(OracleError::SameAsset) => EmaPrice::from(1), + Err(_) => return None, + }; + + let price_lrna_asset_b = AggregatedPriceGetter::get_price(Lrna::get(), asset_b, period, OMNIPOOL_SOURCE); + + let price_lrna_asset_b = match price_lrna_asset_b { + Ok(price) => price.0, + Err(OracleError::SameAsset) => EmaPrice::from(1), + Err(_) => return None, + }; + + let nominator = U128::full_mul(price_asset_a_lrna.n.into(), price_lrna_asset_b.n.into()); + let denominator = U128::full_mul(price_asset_a_lrna.d.into(), price_lrna_asset_b.d.into()); + + let rational_as_u128 = round_to_rational((nominator, denominator), Rounding::Nearest); + let price_in_ema_price = EmaPrice::new(rational_as_u128.0, rational_as_u128.1); + + Some(price_in_ema_price) + } +} + +pub struct PriceAdjustmentAdapter(PhantomData<(Runtime, LMInstance)>); + +impl PriceAdjustment> + for PriceAdjustmentAdapter +where + Runtime: warehouse_liquidity_mining::Config + + pallet_ema_oracle::Config + + pallet_omnipool_liquidity_mining::Config, +{ + type Error = DispatchError; + type PriceAdjustment = FixedU128; + + fn get(global_farm: &GlobalFarmData) -> Result { + let (price, _) = pallet_ema_oracle::Pallet::::get_price( + global_farm.reward_currency.into(), + global_farm.incentivized_asset.into(), //LRNA + OraclePeriod::TenMinutes, + OMNIPOOL_SOURCE, + ) + .map_err(|_| pallet_omnipool_liquidity_mining::Error::::PriceAdjustmentNotAvailable)?; + + FixedU128::checked_from_rational(price.n, price.d).ok_or_else(|| ArithmeticError::Overflow.into()) + } +} + +/// Asset transaction errors. +enum Error { + /// Failed to match fungible. + FailedToMatchFungible, + /// `MultiLocation` to `AccountId` Conversion failed. + AccountIdConversionFailed, + /// `CurrencyId` conversion failed. + CurrencyIdConversionFailed, +} + +impl From for XcmError { + fn from(e: Error) -> Self { + match e { + Error::FailedToMatchFungible => XcmError::FailedToTransactAsset("FailedToMatchFungible"), + Error::AccountIdConversionFailed => XcmError::FailedToTransactAsset("AccountIdConversionFailed"), + Error::CurrencyIdConversionFailed => XcmError::FailedToTransactAsset("CurrencyIdConversionFailed"), + } + } +} + +/// The `TransactAsset` implementation, to handle `MultiAsset` deposit/withdraw, but reroutes deposits and transfers +/// to unsupported accounts to an alternative. +/// +/// Note that teleport related functions are unimplemented. +/// +/// Methods of `DepositFailureHandler` would be called on multi-currency deposit +/// errors. +/// +/// If the asset is known, deposit/withdraw will be handled by `MultiCurrency`, +/// else by `UnknownAsset` if unknown. +/// +/// Taken and modified from `orml_xcm_support`. +/// https://github.com/open-web3-stack/open-runtime-module-library/blob/4ae0372e2c624e6acc98305564b9d395f70814c0/xcm-support/src/currency_adapter.rs#L96-L202 +#[allow(clippy::type_complexity)] +pub struct ReroutingMultiCurrencyAdapter< + MultiCurrency, + UnknownAsset, + Match, + AccountId, + AccountIdConvert, + CurrencyId, + CurrencyIdConvert, + DepositFailureHandler, + RerouteFilter, + RerouteDestination, +>( + PhantomData<( + MultiCurrency, + UnknownAsset, + Match, + AccountId, + AccountIdConvert, + CurrencyId, + CurrencyIdConvert, + DepositFailureHandler, + RerouteFilter, + RerouteDestination, + )>, +); + +impl< + MultiCurrency: orml_traits::MultiCurrency, + UnknownAsset: UnknownAssetT, + Match: MatchesFungible, + AccountId: sp_std::fmt::Debug + Clone, + AccountIdConvert: MoreConvert, + CurrencyId: FullCodec + Eq + PartialEq + Copy + MaybeSerializeDeserialize + Debug, + CurrencyIdConvert: Convert>, + DepositFailureHandler: OnDepositFail, + RerouteFilter: Contains<(CurrencyId, AccountId)>, + RerouteDestination: Get, + > TransactAsset + for ReroutingMultiCurrencyAdapter< + MultiCurrency, + UnknownAsset, + Match, + AccountId, + AccountIdConvert, + CurrencyId, + CurrencyIdConvert, + DepositFailureHandler, + RerouteFilter, + RerouteDestination, + > +{ + fn deposit_asset(asset: &MultiAsset, location: &MultiLocation, _context: &XcmContext) -> Result<(), XcmError> { + match ( + AccountIdConvert::convert_ref(location), + CurrencyIdConvert::convert(asset.clone()), + Match::matches_fungible(asset), + ) { + // known asset + (Ok(who), Some(currency_id), Some(amount)) => { + if RerouteFilter::contains(&(currency_id, who.clone())) { + MultiCurrency::deposit(currency_id, &RerouteDestination::get(), amount) + .or_else(|err| DepositFailureHandler::on_deposit_currency_fail(err, currency_id, &who, amount)) + } else { + MultiCurrency::deposit(currency_id, &who, amount) + .or_else(|err| DepositFailureHandler::on_deposit_currency_fail(err, currency_id, &who, amount)) + } + } + // unknown asset + _ => UnknownAsset::deposit(asset, location) + .or_else(|err| DepositFailureHandler::on_deposit_unknown_asset_fail(err, asset, location)), + } + } + + fn withdraw_asset( + asset: &MultiAsset, + location: &MultiLocation, + _maybe_context: Option<&XcmContext>, + ) -> Result { + UnknownAsset::withdraw(asset, location).or_else(|_| { + let who = AccountIdConvert::convert_ref(location) + .map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; + let currency_id = CurrencyIdConvert::convert(asset.clone()) + .ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; + let amount: MultiCurrency::Balance = Match::matches_fungible(asset) + .ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))? + .saturated_into(); + MultiCurrency::withdraw(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into())) + })?; + + Ok(asset.clone().into()) + } + + fn transfer_asset( + asset: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + _context: &XcmContext, + ) -> Result { + let from_account = + AccountIdConvert::convert_ref(from).map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; + let to_account = + AccountIdConvert::convert_ref(to).map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; + let currency_id = CurrencyIdConvert::convert(asset.clone()) + .ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; + let to_account = if RerouteFilter::contains(&(currency_id, to_account.clone())) { + RerouteDestination::get() + } else { + to_account + }; + let amount: MultiCurrency::Balance = Match::matches_fungible(asset) + .ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))? + .saturated_into(); + MultiCurrency::transfer(currency_id, &from_account, &to_account, amount) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + + Ok(asset.clone().into()) + } +} + +// Dynamic fees volume adapter +pub struct OracleVolume(Balance, Balance); + +impl pallet_dynamic_fees::traits::Volume for OracleVolume { + fn amount_in(&self) -> Balance { + self.0 + } + + fn amount_out(&self) -> Balance { + self.1 + } +} + +pub struct OracleAssetVolumeProvider(PhantomData<(Runtime, Lrna, Period)>); + +impl pallet_dynamic_fees::traits::VolumeProvider + for OracleAssetVolumeProvider +where + Runtime: pallet_ema_oracle::Config, + Lrna: Get, + Period: Get, +{ + type Volume = OracleVolume; + + fn asset_volume(asset_id: AssetId) -> Option { + let entry = + pallet_ema_oracle::Pallet::::get_entry(asset_id, Lrna::get(), Period::get(), OMNIPOOL_SOURCE) + .ok()?; + Some(OracleVolume(entry.volume.a_in, entry.volume.a_out)) + } + + fn asset_liquidity(asset_id: AssetId) -> Option { + let entry = + pallet_ema_oracle::Pallet::::get_entry(asset_id, Lrna::get(), Period::get(), OMNIPOOL_SOURCE) + .ok()?; + Some(entry.liquidity.a) + } +} diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 33571e44e..3ae0dcd95 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "164.0.0" +version = "165.0.0" authors = ["GalacticCouncil"] edition = "2021" license = "Apache 2.0" diff --git a/runtime/hydradx/src/adapters.rs b/runtime/hydradx/src/adapters.rs index 03f45a82d..8b1378917 100644 --- a/runtime/hydradx/src/adapters.rs +++ b/runtime/hydradx/src/adapters.rs @@ -1,439 +1 @@ -use crate::assets::OMNIPOOL_SOURCE; -use codec::FullCodec; -use core::marker::PhantomData; -use frame_support::{ - sp_runtime::{ - traits::{Convert, MaybeSerializeDeserialize, Zero}, - ArithmeticError, DispatchError, FixedPointNumber, FixedU128, SaturatedConversion, - }, - traits::{Contains, Get}, - weights::Weight, -}; -use hydra_dx_math::{ - ema::EmaPrice, - omnipool::types::BalanceUpdate, - support::rational::{round_to_rational, Rounding}, -}; -use hydradx_traits::{ - liquidity_mining::PriceAdjustment, AggregatedOracle, AggregatedPriceOracle, OnLiquidityChangedHandler, - OnTradeHandler, OraclePeriod, PriceOracle, -}; -use orml_xcm_support::{OnDepositFail, UnknownAsset as UnknownAssetT}; -use pallet_circuit_breaker::WeightInfo; -use pallet_ema_oracle::{OnActivityHandler, OracleError, Price}; -use pallet_omnipool::traits::{AssetInfo, ExternalPriceProvider, OmnipoolHooks}; -use polkadot_xcm::latest::prelude::*; -use primitive_types::U128; -use primitives::{AssetId, Balance, BlockNumber}; -use sp_std::fmt::Debug; -use warehouse_liquidity_mining::GlobalFarmData; -use xcm_executor::{ - traits::{Convert as MoreConvert, MatchesFungible, TransactAsset}, - Assets, -}; - -/// Passes on trade and liquidity data from the omnipool to the oracle. -pub struct OmnipoolHookAdapter(PhantomData<(Origin, Lrna, Runtime)>); - -impl OmnipoolHooks for OmnipoolHookAdapter -where - Lrna: Get, - Runtime: pallet_ema_oracle::Config + pallet_circuit_breaker::Config + frame_system::Config, -{ - type Error = DispatchError; - - fn on_liquidity_changed(origin: Origin, asset: AssetInfo) -> Result { - OnActivityHandler::::on_liquidity_changed( - OMNIPOOL_SOURCE, - asset.asset_id, - Lrna::get(), - *asset.delta_changes.delta_reserve, - *asset.delta_changes.delta_hub_reserve, - asset.after.reserve, - asset.after.hub_reserve, - ) - .map_err(|(_, e)| e)?; - - match asset.delta_changes.delta_reserve { - BalanceUpdate::Increase(amount) => pallet_circuit_breaker::Pallet::::ensure_add_liquidity_limit( - origin, - asset.asset_id.into(), - asset.before.reserve.into(), - amount.into(), - )?, - BalanceUpdate::Decrease(amount) => { - pallet_circuit_breaker::Pallet::::ensure_remove_liquidity_limit( - origin, - asset.asset_id.into(), - asset.before.reserve.into(), - amount.into(), - )? - } - }; - - Ok(Self::on_liquidity_changed_weight()) - } - - fn on_trade( - _origin: Origin, - asset_in: AssetInfo, - asset_out: AssetInfo, - ) -> Result { - OnActivityHandler::::on_trade( - OMNIPOOL_SOURCE, - asset_in.asset_id, - Lrna::get(), - *asset_in.delta_changes.delta_reserve, - *asset_in.delta_changes.delta_hub_reserve, - asset_in.after.reserve, - asset_in.after.hub_reserve, - ) - .map_err(|(_, e)| e)?; - - OnActivityHandler::::on_trade( - OMNIPOOL_SOURCE, - Lrna::get(), - asset_out.asset_id, - *asset_out.delta_changes.delta_hub_reserve, - *asset_out.delta_changes.delta_reserve, - asset_out.after.hub_reserve, - asset_out.after.reserve, - ) - .map_err(|(_, e)| e)?; - - let amount_in = *asset_in.delta_changes.delta_reserve; - let amount_out = *asset_out.delta_changes.delta_reserve; - - pallet_circuit_breaker::Pallet::::ensure_pool_state_change_limit( - asset_in.asset_id.into(), - asset_in.before.reserve.into(), - amount_in.into(), - asset_out.asset_id.into(), - asset_out.before.reserve.into(), - amount_out.into(), - )?; - - Ok(Self::on_trade_weight()) - } - - fn on_hub_asset_trade(_origin: Origin, asset: AssetInfo) -> Result { - OnActivityHandler::::on_trade( - OMNIPOOL_SOURCE, - Lrna::get(), - asset.asset_id, - *asset.delta_changes.delta_hub_reserve, - *asset.delta_changes.delta_reserve, - asset.after.hub_reserve, - asset.after.reserve, - ) - .map_err(|(_, e)| e)?; - - let amount_out = *asset.delta_changes.delta_reserve; - - pallet_circuit_breaker::Pallet::::ensure_pool_state_change_limit( - Lrna::get().into(), - Balance::zero().into(), - Balance::zero().into(), - asset.asset_id.into(), - asset.before.reserve.into(), - amount_out.into(), - )?; - - Ok(Self::on_trade_weight()) - } - - fn on_liquidity_changed_weight() -> Weight { - let w1 = OnActivityHandler::::on_liquidity_changed_weight(); - let w2 = ::WeightInfo::ensure_add_liquidity_limit() - .max(::WeightInfo::ensure_remove_liquidity_limit()); - let w3 = ::WeightInfo::on_finalize_single_liquidity_limit_entry(); - w1.saturating_add(w2).saturating_add(w3) - } - - fn on_trade_weight() -> Weight { - let w1 = OnActivityHandler::::on_trade_weight().saturating_mul(2); - let w2 = ::WeightInfo::ensure_pool_state_change_limit(); - let w3 = ::WeightInfo::on_finalize_single_trade_limit_entry(); - w1.saturating_add(w2).saturating_add(w3) - } -} - -/// Passes ema oracle price to the omnipool. -pub struct EmaOraclePriceAdapter(PhantomData<(Period, Runtime)>); - -impl ExternalPriceProvider for EmaOraclePriceAdapter -where - Period: Get, - Runtime: pallet_ema_oracle::Config + pallet_omnipool::Config, -{ - type Error = DispatchError; - - fn get_price(asset_a: AssetId, asset_b: AssetId) -> Result { - let (price, _) = - pallet_ema_oracle::Pallet::::get_price(asset_a, asset_b, Period::get(), OMNIPOOL_SOURCE) - .map_err(|_| pallet_omnipool::Error::::PriceDifferenceTooHigh)?; - Ok(price) - } - - fn get_price_weight() -> Weight { - pallet_ema_oracle::Pallet::::get_price_weight() - } -} - -pub struct OraclePriceProviderAdapterForOmnipool( - PhantomData<(AssetId, AggregatedPriceGetter, Lrna)>, -); - -impl PriceOracle - for OraclePriceProviderAdapterForOmnipool -where - u32: From, - AggregatedPriceGetter: AggregatedPriceOracle, - Lrna: Get, -{ - type Price = EmaPrice; - - fn price(asset_a: AssetId, asset_b: AssetId, period: OraclePeriod) -> Option { - let price_asset_a_lrna = AggregatedPriceGetter::get_price(asset_a, Lrna::get(), period, OMNIPOOL_SOURCE); - - let price_asset_a_lrna = match price_asset_a_lrna { - Ok(price) => price.0, - Err(OracleError::SameAsset) => EmaPrice::from(1), - Err(_) => return None, - }; - - let price_lrna_asset_b = AggregatedPriceGetter::get_price(Lrna::get(), asset_b, period, OMNIPOOL_SOURCE); - - let price_lrna_asset_b = match price_lrna_asset_b { - Ok(price) => price.0, - Err(OracleError::SameAsset) => EmaPrice::from(1), - Err(_) => return None, - }; - - let nominator = U128::full_mul(price_asset_a_lrna.n.into(), price_lrna_asset_b.n.into()); - let denominator = U128::full_mul(price_asset_a_lrna.d.into(), price_lrna_asset_b.d.into()); - - let rational_as_u128 = round_to_rational((nominator, denominator), Rounding::Nearest); - let price_in_ema_price = EmaPrice::new(rational_as_u128.0, rational_as_u128.1); - - Some(price_in_ema_price) - } -} - -pub struct PriceAdjustmentAdapter(PhantomData<(Runtime, LMInstance)>); - -impl PriceAdjustment> - for PriceAdjustmentAdapter -where - Runtime: warehouse_liquidity_mining::Config - + pallet_ema_oracle::Config - + pallet_omnipool_liquidity_mining::Config, -{ - type Error = DispatchError; - type PriceAdjustment = FixedU128; - - fn get(global_farm: &GlobalFarmData) -> Result { - let (price, _) = pallet_ema_oracle::Pallet::::get_price( - global_farm.reward_currency.into(), - global_farm.incentivized_asset.into(), //LRNA - OraclePeriod::TenMinutes, - OMNIPOOL_SOURCE, - ) - .map_err(|_| pallet_omnipool_liquidity_mining::Error::::PriceAdjustmentNotAvailable)?; - - FixedU128::checked_from_rational(price.n, price.d).ok_or_else(|| ArithmeticError::Overflow.into()) - } -} - -/// Asset transaction errors. -enum Error { - /// Failed to match fungible. - FailedToMatchFungible, - /// `MultiLocation` to `AccountId` Conversion failed. - AccountIdConversionFailed, - /// `CurrencyId` conversion failed. - CurrencyIdConversionFailed, -} - -impl From for XcmError { - fn from(e: Error) -> Self { - match e { - Error::FailedToMatchFungible => XcmError::FailedToTransactAsset("FailedToMatchFungible"), - Error::AccountIdConversionFailed => XcmError::FailedToTransactAsset("AccountIdConversionFailed"), - Error::CurrencyIdConversionFailed => XcmError::FailedToTransactAsset("CurrencyIdConversionFailed"), - } - } -} - -/// The `TransactAsset` implementation, to handle `MultiAsset` deposit/withdraw, but reroutes deposits and transfers -/// to unsupported accounts to an alternative. -/// -/// Note that teleport related functions are unimplemented. -/// -/// Methods of `DepositFailureHandler` would be called on multi-currency deposit -/// errors. -/// -/// If the asset is known, deposit/withdraw will be handled by `MultiCurrency`, -/// else by `UnknownAsset` if unknown. -/// -/// Taken and modified from `orml_xcm_support`. -/// https://github.com/open-web3-stack/open-runtime-module-library/blob/4ae0372e2c624e6acc98305564b9d395f70814c0/xcm-support/src/currency_adapter.rs#L96-L202 -#[allow(clippy::type_complexity)] -pub struct ReroutingMultiCurrencyAdapter< - MultiCurrency, - UnknownAsset, - Match, - AccountId, - AccountIdConvert, - CurrencyId, - CurrencyIdConvert, - DepositFailureHandler, - RerouteFilter, - RerouteDestination, ->( - PhantomData<( - MultiCurrency, - UnknownAsset, - Match, - AccountId, - AccountIdConvert, - CurrencyId, - CurrencyIdConvert, - DepositFailureHandler, - RerouteFilter, - RerouteDestination, - )>, -); - -impl< - MultiCurrency: orml_traits::MultiCurrency, - UnknownAsset: UnknownAssetT, - Match: MatchesFungible, - AccountId: sp_std::fmt::Debug + Clone, - AccountIdConvert: MoreConvert, - CurrencyId: FullCodec + Eq + PartialEq + Copy + MaybeSerializeDeserialize + Debug, - CurrencyIdConvert: Convert>, - DepositFailureHandler: OnDepositFail, - RerouteFilter: Contains<(CurrencyId, AccountId)>, - RerouteDestination: Get, - > TransactAsset - for ReroutingMultiCurrencyAdapter< - MultiCurrency, - UnknownAsset, - Match, - AccountId, - AccountIdConvert, - CurrencyId, - CurrencyIdConvert, - DepositFailureHandler, - RerouteFilter, - RerouteDestination, - > -{ - fn deposit_asset(asset: &MultiAsset, location: &MultiLocation, _context: &XcmContext) -> Result<(), XcmError> { - match ( - AccountIdConvert::convert_ref(location), - CurrencyIdConvert::convert(asset.clone()), - Match::matches_fungible(asset), - ) { - // known asset - (Ok(who), Some(currency_id), Some(amount)) => { - if RerouteFilter::contains(&(currency_id, who.clone())) { - MultiCurrency::deposit(currency_id, &RerouteDestination::get(), amount) - .or_else(|err| DepositFailureHandler::on_deposit_currency_fail(err, currency_id, &who, amount)) - } else { - MultiCurrency::deposit(currency_id, &who, amount) - .or_else(|err| DepositFailureHandler::on_deposit_currency_fail(err, currency_id, &who, amount)) - } - } - // unknown asset - _ => UnknownAsset::deposit(asset, location) - .or_else(|err| DepositFailureHandler::on_deposit_unknown_asset_fail(err, asset, location)), - } - } - - fn withdraw_asset( - asset: &MultiAsset, - location: &MultiLocation, - _maybe_context: Option<&XcmContext>, - ) -> Result { - UnknownAsset::withdraw(asset, location).or_else(|_| { - let who = AccountIdConvert::convert_ref(location) - .map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; - let currency_id = CurrencyIdConvert::convert(asset.clone()) - .ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; - let amount: MultiCurrency::Balance = Match::matches_fungible(asset) - .ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))? - .saturated_into(); - MultiCurrency::withdraw(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into())) - })?; - - Ok(asset.clone().into()) - } - - fn transfer_asset( - asset: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, - _context: &XcmContext, - ) -> Result { - let from_account = - AccountIdConvert::convert_ref(from).map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; - let to_account = - AccountIdConvert::convert_ref(to).map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; - let currency_id = CurrencyIdConvert::convert(asset.clone()) - .ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; - let to_account = if RerouteFilter::contains(&(currency_id, to_account.clone())) { - RerouteDestination::get() - } else { - to_account - }; - let amount: MultiCurrency::Balance = Match::matches_fungible(asset) - .ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))? - .saturated_into(); - MultiCurrency::transfer(currency_id, &from_account, &to_account, amount) - .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; - - Ok(asset.clone().into()) - } -} - -// Dynamic fees volume adapter -pub struct OracleVolume(Balance, Balance); - -impl pallet_dynamic_fees::traits::Volume for OracleVolume { - fn amount_in(&self) -> Balance { - self.0 - } - - fn amount_out(&self) -> Balance { - self.1 - } -} - -pub struct OracleAssetVolumeProvider(PhantomData<(Runtime, Lrna, Period)>); - -impl pallet_dynamic_fees::traits::VolumeProvider - for OracleAssetVolumeProvider -where - Runtime: pallet_ema_oracle::Config, - Lrna: Get, - Period: Get, -{ - type Volume = OracleVolume; - - fn asset_volume(asset_id: AssetId) -> Option { - let entry = - pallet_ema_oracle::Pallet::::get_entry(asset_id, Lrna::get(), Period::get(), OMNIPOOL_SOURCE) - .ok()?; - Some(OracleVolume(entry.volume.a_in, entry.volume.a_out)) - } - - fn asset_liquidity(asset_id: AssetId) -> Option { - let entry = - pallet_ema_oracle::Pallet::::get_entry(asset_id, Lrna::get(), Period::get(), OMNIPOOL_SOURCE) - .ok()?; - Some(entry.liquidity.a) - } -} diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 2e30ee3ad..ed801dbec 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -16,17 +16,22 @@ // limitations under the License. use super::*; -use crate::adapters::{EmaOraclePriceAdapter, OmnipoolHookAdapter, OraclePriceProviderAdapterForOmnipool}; use crate::system::NativeAssetId; -use hydradx_adapters::inspect::MultiInspectAdapter; +use hydradx_adapters::{ + inspect::MultiInspectAdapter, EmaOraclePriceAdapter, OmnipoolHookAdapter, OracleAssetVolumeProvider, + OraclePriceProviderAdapterForOmnipool, PriceAdjustmentAdapter, +}; use hydradx_traits::{OraclePeriod, Source}; use pallet_currencies::BasicCurrencyAdapter; use pallet_dca::RelayChainBlockHashProvider; use pallet_omnipool::traits::EnsurePriceWithin; use pallet_otc::NamedReserveIdentifier; use pallet_transaction_multi_payment::{AddTxAssetOnAccount, RemoveTxAssetOnKilled}; -use primitives::constants::currency::{NATIVE_EXISTENTIAL_DEPOSIT, UNITS}; +use primitives::constants::{ + chain::OMNIPOOL_SOURCE, + currency::{NATIVE_EXISTENTIAL_DEPOSIT, UNITS}, +}; use frame_support::{ parameter_types, @@ -191,9 +196,6 @@ impl pallet_uniques::Config for Runtime { type WeightInfo = (); } -/// The source of the data for the oracle. -pub const OMNIPOOL_SOURCE: [u8; 8] = *b"omnipool"; - parameter_types! { pub const LRNA: AssetId = 1; pub const StableAssetId: AssetId = 2; @@ -341,7 +343,7 @@ impl warehouse_liquidity_mining::Config for Run type MaxYieldFarmsPerGlobalFarm = MaxYieldFarmsPerGlobalFarm; type AssetRegistry = AssetRegistry; type NonDustableWhitelistHandler = Duster; - type PriceAdjustment = adapters::PriceAdjustmentAdapter; + type PriceAdjustment = PriceAdjustmentAdapter; } parameter_types! { @@ -474,7 +476,7 @@ impl pallet_dynamic_fees::Config for Runtime { type BlockNumberProvider = System; type Fee = Permill; type AssetId = AssetId; - type Oracle = adapters::OracleAssetVolumeProvider; + type Oracle = OracleAssetVolumeProvider; type AssetFeeParameters = AssetFeeParams; type ProtocolFeeParameters = ProtocolFeeParams; } diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index a012dedf4..614c8e3de 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -96,7 +96,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 164, + spec_version: 165, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index a9a7c5f66..f14ce2e24 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -1,7 +1,6 @@ use super::*; -use crate::adapters::ReroutingMultiCurrencyAdapter; -use hydradx_adapters::{MultiCurrencyTrader, ToFeeReceiver}; +use hydradx_adapters::{MultiCurrencyTrader, ReroutingMultiCurrencyAdapter, ToFeeReceiver}; use pallet_transaction_multi_payment::DepositAll; use primitives::AssetId; // shadow glob import of polkadot_xcm::v3::prelude::AssetId