diff --git a/prdoc/pr_2903.prdoc b/prdoc/pr_2903.prdoc new file mode 100644 index 0000000000000..dd22fa0eea375 --- /dev/null +++ b/prdoc/pr_2903.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Implement `ConversionToAssetBalance` in asset-rate" + +doc: + - audience: Runtime Dev + description: | + Implements the `ConversionToAssetBalance` trait to the asset-rate pallet. + + Previously only the `ConversionFromAssetBalance` trait was implemented, which would allow to convert an asset balance into the corresponding native balance. + + The `ConversionToAssetBalance` allows to use pallet-asset-rate, e.g., as a mechanism to charge XCM fees in an asset that is not the native. +crates: [ ] + diff --git a/substrate/frame/asset-rate/src/lib.rs b/substrate/frame/asset-rate/src/lib.rs index d4afca8b73c4b..befabfe54aa0e 100644 --- a/substrate/frame/asset-rate/src/lib.rs +++ b/substrate/frame/asset-rate/src/lib.rs @@ -59,8 +59,14 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{fungible::Inspect, tokens::ConversionFromAssetBalance}; -use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128}; +use frame_support::traits::{ + fungible::Inspect, + tokens::{ConversionFromAssetBalance, ConversionToAssetBalance}, +}; +use sp_runtime::{ + traits::{CheckedDiv, Zero}, + FixedPointNumber, FixedU128, +}; use sp_std::boxed::Box; pub use pallet::*; @@ -144,6 +150,8 @@ pub mod pallet { UnknownAssetKind, /// The given asset ID already has an assigned conversion rate and cannot be re-created. AlreadyExists, + /// Overflow ocurred when calculating the inverse rate. + Overflow, } #[pallet::call] @@ -246,3 +254,25 @@ where pallet::ConversionRateToNative::::set(asset_id.clone(), Some(1.into())); } } + +/// Exposes conversion of a native balance to an asset balance. +impl ConversionToAssetBalance, AssetKindOf, BalanceOf> for Pallet +where + T: Config, +{ + type Error = pallet::Error; + + fn to_asset_balance( + balance: BalanceOf, + asset_kind: AssetKindOf, + ) -> Result, pallet::Error> { + let rate = pallet::ConversionRateToNative::::get(asset_kind) + .ok_or(pallet::Error::::UnknownAssetKind.into())?; + + // We cannot use `saturating_div` here so we use `checked_div`. + Ok(FixedU128::from_u32(1) + .checked_div(&rate) + .ok_or(pallet::Error::::Overflow.into())? + .saturating_mul_int(balance)) + } +} diff --git a/substrate/frame/asset-rate/src/tests.rs b/substrate/frame/asset-rate/src/tests.rs index 452265476093b..fc7bf2f150311 100644 --- a/substrate/frame/asset-rate/src/tests.rs +++ b/substrate/frame/asset-rate/src/tests.rs @@ -131,12 +131,19 @@ fn convert_works() { FixedU128::from_float(2.51) )); - let conversion = , ::AssetKind, BalanceOf, >>::from_asset_balance(10, ASSET_ID); - assert_eq!(conversion.expect("Conversion rate exists for asset"), 25); + assert_eq!(conversion_from_asset.expect("Conversion rate exists for asset"), 25); + + let conversion_to_asset = , + ::AssetKind, + BalanceOf, + >>::to_asset_balance(25, ASSET_ID); + assert_eq!(conversion_to_asset.expect("Conversion rate exists for asset"), 9); }); } @@ -151,3 +158,21 @@ fn convert_unknown_throws() { assert!(conversion.is_err()); }); } + +#[test] +fn convert_overflow_throws() { + new_test_ext().execute_with(|| { + assert_ok!(AssetRate::create( + RuntimeOrigin::root(), + Box::new(ASSET_ID), + FixedU128::from_u32(0) + )); + + let conversion = , + ::AssetKind, + BalanceOf, + >>::to_asset_balance(10, ASSET_ID); + assert!(conversion.is_err()); + }); +}