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

Slpx supports hydration oracle #1658

Merged
merged 10 commits into from
Feb 13, 2025
123 changes: 121 additions & 2 deletions pallets/slpx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@
#![cfg_attr(not(feature = "std"), no_std)]
use crate::types::{
AccountIdOf, BalanceOf, CurrencyIdOf, EthereumCallConfiguration, EthereumXcmCall,
EthereumXcmTransaction, EthereumXcmTransactionV2, MoonbeamCall, OracleConfig, Order,
OrderCaller, OrderType, SupportChain, TargetChain, EVM_FUNCTION_SELECTOR, MAX_GAS_LIMIT,
EthereumXcmTransaction, EthereumXcmTransactionV2, HydrationOracleConfig, MoonbeamCall,
OracleConfig, Order, OrderCaller, OrderType, SupportChain, TargetChain, EVM_FUNCTION_SELECTOR,
MAX_GAS_LIMIT,
};
#[cfg(feature = "polkadot")]
use crate::types::{
HYDRATION_CALL_FEE, HYDRATION_CALL_WEIGHT, HYDRATION_EMA_ORACLE_CALL_INDEX,
HYDRATION_EMA_ORACLE_PALLET_INDEX,
};
use bifrost_asset_registry::AssetMetadata;
use bifrost_primitives::{
Expand Down Expand Up @@ -63,6 +69,8 @@ use sp_runtime::{
};
use sp_std::{vec, vec::Vec};
use xcm::v4::{prelude::*, Location};
#[cfg(feature = "polkadot")]
use xcm::{DoubleEncoded, VersionedLocation};
use xcm_builder::{DescribeAllTerminal, DescribeFamily, HashedDescription};
use xcm_executor::traits::ConvertLocation;

Expand Down Expand Up @@ -240,6 +248,11 @@ pub mod pallet {
period: BlockNumberFor<T>,
tokens: BoundedVec<(CurrencyId, H160), ConstU32<10>>,
},
/// Set Hydration Oracle Config
SetHydrationOracleConfig {
period: BlockNumberFor<T>,
tokens: BoundedVec<(CurrencyId, Location, Location), ConstU32<10>>,
},
}

#[pallet::error]
Expand Down Expand Up @@ -336,6 +349,11 @@ pub mod pallet {
OptionQuery,
>;

/// Hydration chain oracle configuration
#[pallet::storage]
pub type HydrationOracle<T: Config> =
StorageValue<_, HydrationOracleConfig<BlockNumberFor<T>>, OptionQuery>;

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_idle(_: BlockNumberFor<T>, limit: Weight) -> Weight {
Expand Down Expand Up @@ -367,6 +385,11 @@ pub mod pallet {
if !is_handle_xcm_oracle {
let _ = Self::handle_hyperbridge_oracle(current_block_number, &mut weight);
}

#[cfg(feature = "polkadot")]
if !is_handle_xcm_oracle {
let _ = Self::handle_hydration_oracle(current_block_number, &mut weight);
}
weight
}
}
Expand Down Expand Up @@ -863,6 +886,31 @@ pub mod pallet {
});
Ok(().into())
}

/// Set Hydration Oracle Config
/// Parameters:
/// - `period`: The period of Sending Xcm
/// - `tokens`: The tokens of the oracle
#[pallet::call_index(16)]
#[pallet::weight(<T as Config>::WeightInfo::set_transfer_to_fee())]
pub fn set_hydration_oracle(
origin: OriginFor<T>,
period: BlockNumberFor<T>,
tokens: BoundedVec<(CurrencyId, Location, Location), ConstU32<10>>,
) -> DispatchResultWithPostInfo {
T::ControlOrigin::ensure_origin(origin)?;
if tokens.is_empty() {
HydrationOracle::<T>::set(None);
} else {
HydrationOracle::<T>::put(HydrationOracleConfig {
period,
last_block: Default::default(),
tokens: tokens.clone(),
});
}
Self::deposit_event(Event::SetHydrationOracleConfig { period, tokens });
Ok(().into())
}
}
}

Expand Down Expand Up @@ -1486,6 +1534,77 @@ impl<T: Config> Pallet<T> {
}
return Ok(());
}

#[cfg(feature = "polkadot")]
#[transactional]
pub fn handle_hydration_oracle(
current_block_number: BlockNumberFor<T>,
weight: &mut Weight,
) -> DispatchResult {
if let Some(HydrationOracleConfig {
period,
last_block,
tokens,
}) = HydrationOracle::<T>::get()
{
if last_block + period < current_block_number {
for (currency, location_a, location_b) in tokens.clone() {
let refund_location = Location::new(
0,
[AccountId32 {
network: None,
id: Sibling::from(2030).into_account_truncating(),
}],
);
let fee_location = Location::here();
let asset = Asset {
id: AssetId(fee_location),
fun: Fungible(HYDRATION_CALL_FEE),
};
let assets: Assets = Assets::from(asset.clone());
let require_weight_at_most = HYDRATION_CALL_WEIGHT;
let staking_currency_amount =
T::VtokenMintingInterface::get_token_pool(currency);
let v_currency_id = currency
.to_vtoken()
.map_err(|_| Error::<T>::ErrorConvertVtoken)?;

let v_currency_total_supply = T::MultiCurrency::total_issuance(v_currency_id);
let mut call_data = HYDRATION_EMA_ORACLE_PALLET_INDEX.encode();
call_data.extend(HYDRATION_EMA_ORACLE_CALL_INDEX.encode());
call_data.extend(VersionedLocation::V4(location_a).encode());
call_data.extend(VersionedLocation::V4(location_b).encode());
call_data.extend(
(
staking_currency_amount.saturated_into::<u128>(),
v_currency_total_supply.saturated_into::<u128>(),
)
.encode(),
);
let call: DoubleEncoded<()> = call_data.into();
let xcm_message = Xcm::builder()
.withdraw_asset(assets)
.buy_execution(asset, WeightLimit::Unlimited)
.transact(OriginKind::SovereignAccount, require_weight_at_most, call)
.refund_surplus()
.deposit_asset(AssetFilter::Wild(WildAsset::All), refund_location)
.build();
let dest_location = Location::new(1, [Parachain(HydrationChainId::get())]);
let (ticket, _price) =
T::XcmSender::validate(&mut Some(dest_location), &mut Some(xcm_message))
.map_err(|_| Error::<T>::ErrorValidating)?;
T::XcmSender::deliver(ticket).map_err(|_| Error::<T>::ErrorDelivering)?;
*weight = weight.saturating_add(T::DbWeight::get().reads_writes(6, 2));
}
}
HydrationOracle::<T>::put(HydrationOracleConfig {
period,
last_block: current_block_number,
tokens,
});
}
return Ok(());
}
}

// Functions to be called by other pallets.
Expand Down
21 changes: 21 additions & 0 deletions pallets/slpx/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,3 +546,24 @@ fn test_set_hyperbridge_oracle_config() {
);
})
}

#[test]
fn test_set_hydration_oracle_config() {
new_test_ext().execute_with(|| {
assert_ok!(Slpx::set_hydration_oracle(
RuntimeOrigin::root(),
1,
BoundedVec::try_from(vec![(BNC, Location::here(), Location::here())]).unwrap(),
));

assert_eq!(
HydrationOracle::<Test>::get().unwrap(),
HydrationOracleConfig {
period: 1u32.into(),
last_block: 0u32.into(),
tokens: BoundedVec::try_from(vec![(BNC, Location::here(), Location::here())])
.unwrap(),
}
);
})
}
18 changes: 18 additions & 0 deletions pallets/slpx/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use sp_core::{H160, H256, U256};
use sp_runtime::{traits::ConstU32, BoundedVec, RuntimeDebug};
use sp_std::vec::Vec;
use xcm::prelude::Weight;
use xcm::v4::Location;

/// Max. allowed size of 65_536 bytes.
pub const MAX_ETHEREUM_XCM_INPUT_SIZE: u32 = 2u32.pow(16);
Expand All @@ -36,6 +37,12 @@ pub const MAX_GAS_LIMIT: u32 = 720_000;
/// EVM function selector: setTokenAmount(bytes2,uint256,uint256)
pub const EVM_FUNCTION_SELECTOR: [u8; 4] = [154, 65, 185, 36];

/// Hydration EMA Oracle pallet index and call index
pub const HYDRATION_EMA_ORACLE_PALLET_INDEX: u8 = 202;
pub const HYDRATION_EMA_ORACLE_CALL_INDEX: u8 = 2;
pub const HYDRATION_CALL_FEE: u128 = 2_000_000_000_000;
pub const HYDRATION_CALL_WEIGHT: Weight = Weight::from_parts(10_000_000_000, 30_000);

pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type CurrencyIdOf<T> = <<T as pallet::Config>::MultiCurrency as MultiCurrency<
<T as frame_system::Config>::AccountId,
Expand Down Expand Up @@ -166,3 +173,14 @@ pub struct OracleConfig<AccountId, Balance, BlockNumber> {
/// Token list
pub tokens: BoundedVec<(CurrencyId, H160), ConstU32<10>>,
}

/// HyperBridge Oracle Config
#[derive(Encode, Decode, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct HydrationOracleConfig<BlockNumber> {
/// Wait for the period to call XCM once
pub period: BlockNumber,
/// Block number of the last call
pub last_block: BlockNumber,
/// Token list
pub tokens: BoundedVec<(CurrencyId, Location, Location), ConstU32<10>>,
}