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

Deposit xcm fees to treasury #1036

Merged
merged 13 commits into from
Dec 2, 2021
37 changes: 34 additions & 3 deletions primitives/xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{
traits::{Get, OriginTrait},
traits::{tokens::fungibles::Mutate, Get, OriginTrait},
weights::{constants::WEIGHT_PER_SECOND, Weight},
};
use xcm::latest::{
Expand All @@ -29,8 +29,7 @@ use xcm::latest::{
MultiAsset, MultiLocation, NetworkId,
};
use xcm_builder::TakeRevenue;
use xcm_executor::traits::FilterAssetLocation;
use xcm_executor::traits::WeightTrader;
use xcm_executor::traits::{FilterAssetLocation, MatchesFungibles, WeightTrader};

use sp_std::borrow::Borrow;
use sp_std::{convert::TryInto, marker::PhantomData};
Expand Down Expand Up @@ -215,6 +214,20 @@ impl<
}
}

impl<
AssetId: From<AssetType> + Clone,
AssetType: From<MultiLocation> + Clone,
AssetIdInfoGetter: UnitsToWeightRatio<AssetId>,
R: TakeRevenue,
> Drop for FirstAssetTrader<AssetId, AssetType, AssetIdInfoGetter, R>
{
fn drop(&mut self) {
if let Some((id, amount, _)) = self.1.clone() {
R::take_revenue((id, amount).into());
}
}
}

pub trait Reserve {
/// Returns assets reserve location.
fn reserve(&self) -> Option<MultiLocation>;
Expand Down Expand Up @@ -296,3 +309,21 @@ pub trait AccountIdToCurrencyId<Account, CurrencyId> {
// Get assetId from account
fn account_to_currency_id(account: Account) -> Option<CurrencyId>;
}

pub struct XcmFeesToAccount<Assets, Matcher, AccountId, ReceiverAccount>(
PhantomData<(Assets, Matcher, AccountId, ReceiverAccount)>,
);
impl<
Assets: Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
ReceiverAccount: Get<AccountId>,
> TakeRevenue for XcmFeesToAccount<Assets, Matcher, AccountId, ReceiverAccount>
{
fn take_revenue(revenue: MultiAsset) {
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(&revenue) {
girazoki marked this conversation as resolved.
Show resolved Hide resolved
let ok = Assets::mint_into(asset_id, &ReceiverAccount::get(), amount).is_ok();
debug_assert!(ok, "`mint_into` cannot generally fail; qed");
}
}
}
58 changes: 39 additions & 19 deletions runtime/moonbase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ use fp_rpc::TransactionStatus;
use pallet_evm_precompile_assets_erc20::AccountIdAssetIdConversion;

use account::AccountId20;

use sp_runtime::traits::Hash as THash;

use frame_support::{
Expand Down Expand Up @@ -97,17 +96,19 @@ use sp_std::{
#[cfg(feature = "std")]
use sp_version::NativeVersion;
use sp_version::RuntimeVersion;
use xcm::v1::{
BodyId,
Junction::{PalletInstance, Parachain},
Junctions, MultiLocation, NetworkId,
};
use xcm::latest::prelude::*;

use nimbus_primitives::{CanAuthor, NimbusId};

mod precompiles;
use precompiles::{MoonbasePrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX};

use xcm_primitives::{
AccountIdToCurrencyId, AccountIdToMultiLocation, AsAssetType, FirstAssetTrader,
MultiNativeAsset, SignedToAccountId20, UtilityAvailableCalls, UtilityEncodeCall,
XcmFeesToAccount, XcmTransact,
};

#[cfg(any(feature = "std", test))]
pub use sp_runtime::BuildStorage;

Expand Down Expand Up @@ -971,7 +972,7 @@ pub type FungiblesTransactor = FungiblesAdapter<
ConvertedConcreteAssetId<
AssetId,
Balance,
xcm_primitives::AsAssetType<AssetId, AssetType, AssetManager>,
AsAssetType<AssetId, AssetType, AssetManager>,
JustTry,
>,
),
Expand Down Expand Up @@ -1050,6 +1051,26 @@ pub type XcmBarrier = (
AllowSubscriptionsFrom<Everything>,
);

parameter_types! {
/// Xcm fees will go to the treasury account
pub TreasuryAccount:AccountId = Treasury::account_id();
girazoki marked this conversation as resolved.
Show resolved Hide resolved
}

/// This is the struct that will handle the revenue from xcm fees
pub type XcmFeesToTreasury = XcmFeesToAccount<
girazoki marked this conversation as resolved.
Show resolved Hide resolved
Assets,
(
ConvertedConcreteAssetId<
AssetId,
Balance,
AsAssetType<AssetId, AssetType, AssetManager>,
JustTry,
>,
),
AccountId,
TreasuryAccount,
girazoki marked this conversation as resolved.
Show resolved Hide resolved
>;

pub struct XcmExecutorConfig;
impl xcm_executor::Config for XcmExecutorConfig {
type Call = Call;
Expand All @@ -1058,7 +1079,7 @@ impl xcm_executor::Config for XcmExecutorConfig {
type AssetTransactor = AssetTransactors;
type OriginConverter = XcmOriginToTransactDispatchOrigin;
// Filter to the reserve withdraw operations
type IsReserve = xcm_primitives::MultiNativeAsset;
type IsReserve = MultiNativeAsset;
type IsTeleporter = (); // No teleport
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = XcmBarrier;
Expand All @@ -1075,7 +1096,7 @@ impl xcm_executor::Config for XcmExecutorConfig {
Balances,
DealWithFees<Runtime>,
>,
xcm_primitives::FirstAssetTrader<AssetId, AssetType, AssetManager, ()>,
FirstAssetTrader<AssetId, AssetType, AssetManager, XcmFeesToTreasury>,
girazoki marked this conversation as resolved.
Show resolved Hide resolved
);
type ResponseHandler = PolkadotXcm;
type SubscriptionService = PolkadotXcm;
Expand All @@ -1090,8 +1111,7 @@ parameter_types! {
}

// Converts a Signed Local Origin into a MultiLocation
pub type LocalOriginToLocation =
xcm_primitives::SignedToAccountId20<Origin, AccountId, RelayNetwork>;
pub type LocalOriginToLocation = SignedToAccountId20<Origin, AccountId, RelayNetwork>;

/// The means for routing XCM messages which are not for local execution into the right message
/// queues.
Expand Down Expand Up @@ -1278,7 +1298,7 @@ pub enum CurrencyId {
OtherReserve(AssetId),
}

impl xcm_primitives::AccountIdToCurrencyId<AccountId, CurrencyId> for Runtime {
impl AccountIdToCurrencyId<AccountId, CurrencyId> for Runtime {
fn account_to_currency_id(account: AccountId) -> Option<CurrencyId> {
match account {
// the self-reserve currency is identified by the pallet-balances address
Expand Down Expand Up @@ -1324,9 +1344,9 @@ impl orml_xtokens::Config for Runtime {
type Event = Event;
type Balance = Balance;
type CurrencyId = CurrencyId;
type AccountIdToMultiLocation = xcm_primitives::AccountIdToMultiLocation<AccountId>;
type AccountIdToMultiLocation = AccountIdToMultiLocation<AccountId>;
type CurrencyIdConvert =
CurrencyIdtoMultiLocation<xcm_primitives::AsAssetType<AssetId, AssetType, AssetManager>>;
CurrencyIdtoMultiLocation<AsAssetType<AssetId, AssetType, AssetManager>>;
type XcmExecutor = XcmExecutor;
type SelfLocation = SelfLocation;
type Weigher = XcmWeigher;
Expand All @@ -1352,8 +1372,8 @@ impl TryFrom<u8> for Transactors {
}
}

impl xcm_primitives::UtilityEncodeCall for Transactors {
fn encode_call(self, call: xcm_primitives::UtilityAvailableCalls) -> Vec<u8> {
impl UtilityEncodeCall for Transactors {
fn encode_call(self, call: UtilityAvailableCalls) -> Vec<u8> {
match self {
// Shall we use westend for moonbase? The tests are probably based on rococo
// but moonbase-alpha is attached to westend-runtime I think
Expand All @@ -1362,7 +1382,7 @@ impl xcm_primitives::UtilityEncodeCall for Transactors {
}
}

impl xcm_primitives::XcmTransact for Transactors {
impl XcmTransact for Transactors {
fn destination(self) -> MultiLocation {
match self {
Transactors::Relay => MultiLocation::parent(),
Expand All @@ -1377,9 +1397,9 @@ impl xcm_transactor::Config for Runtime {
type DerivativeAddressRegistrationOrigin = EnsureRoot<AccountId>;
type SovereignAccountDispatcherOrigin = EnsureRoot<AccountId>;
type CurrencyId = CurrencyId;
type AccountIdToMultiLocation = xcm_primitives::AccountIdToMultiLocation<AccountId>;
type AccountIdToMultiLocation = AccountIdToMultiLocation<AccountId>;
type CurrencyIdToMultiLocation =
CurrencyIdtoMultiLocation<xcm_primitives::AsAssetType<AssetId, AssetType, AssetManager>>;
CurrencyIdtoMultiLocation<AsAssetType<AssetId, AssetType, AssetManager>>;
type XcmExecutor = XcmExecutor;
type XcmSender = XcmRouter;
type SelfLocation = SelfLocation;
Expand Down
1 change: 1 addition & 0 deletions runtime/moonbase/tests/xcm_mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub fn relay_ext() -> sp_io::TestExternalities {
pub type RelayChainPalletXcm = pallet_xcm::Pallet<relay_chain::Runtime>;
pub type ParachainPalletXcm = pallet_xcm::Pallet<parachain::Runtime>;
pub type Assets = pallet_assets::Pallet<parachain::Runtime>;
pub type Treasury = pallet_treasury::Pallet<parachain::Runtime>;
pub type AssetManager = pallet_asset_manager::Pallet<parachain::Runtime>;
pub type XTokens = orml_xtokens::Pallet<parachain::Runtime>;
pub type RelayBalances = pallet_balances::Pallet<relay_chain::Runtime>;
Expand Down
53 changes: 51 additions & 2 deletions runtime/moonbase/tests/xcm_mock/parachain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ use frame_support::{
construct_runtime, parameter_types,
traits::{Everything, Get, Nothing, PalletInfo as PalletInfoTrait},
weights::Weight,
PalletId,
};

use frame_system::EnsureRoot;
use parity_scale_codec::{Decode, Encode};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{Hash, IdentityLookup},
Permill,
};
use sp_std::{convert::TryFrom, prelude::*};
use xcm::{latest::prelude::*, Version as XcmVersion, VersionedXcm};
Expand Down Expand Up @@ -218,6 +221,27 @@ pub type Barrier = (
// Subscriptions for version tracking are OK.
AllowSubscriptionsFrom<Everything>,
);

parameter_types! {
/// Xcm fees will go to the treasury account
pub TreasuryAccount:AccountId = Treasury::account_id();
}

/// This is the struct that will handle the revenue from xcm fees
pub type XcmFeesToTreasury = xcm_primitives::XcmFeesToAccount<
Assets,
(
ConvertedConcreteAssetId<
AssetId,
Balance,
xcm_primitives::AsAssetType<AssetId, AssetType, AssetManager>,
JustTry,
>,
),
AccountId,
TreasuryAccount,
>;

parameter_types! {
// We cannot skip the native trader for some specific tests, so we will have to work with
// a native trader that charges same number of units as weight
Expand Down Expand Up @@ -249,7 +273,7 @@ impl Config for XcmConfig {
type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
type Trader = (
FixedRateOfFungible<ParaTokensPerSecond, ()>,
xcm_primitives::FirstAssetTrader<AssetId, AssetType, AssetManager, ()>,
xcm_primitives::FirstAssetTrader<AssetId, AssetType, AssetManager, XcmFeesToTreasury>,
);

type ResponseHandler = PolkadotXcm;
Expand Down Expand Up @@ -313,6 +337,31 @@ impl orml_xtokens::Config for Runtime {
type LocationInverter = LocationInverter<Ancestry>;
}

parameter_types! {
pub const ProposalBond: Permill = Permill::from_percent(5);
pub const ProposalBondMinimum: Balance = 0;
pub const SpendPeriod: u64 = 0;
pub const TreasuryId: PalletId = PalletId(*b"pc/trsry");
pub const MaxApprovals: u32 = 100;
}

impl pallet_treasury::Config for Runtime {
type PalletId = TreasuryId;
type Currency = Balances;
type ApproveOrigin = EnsureRoot<AccountId>;
type RejectOrigin = EnsureRoot<AccountId>;
type Event = Event;
type OnSlash = Treasury;
type ProposalBond = ProposalBond;
type ProposalBondMinimum = ProposalBondMinimum;
type SpendPeriod = SpendPeriod;
type Burn = ();
type BurnDestination = ();
type MaxApprovals = MaxApprovals;
type WeightInfo = ();
type SpendFunds = ();
}

#[frame_support::pallet]
pub mod mock_msg_queue {
use super::*;
Expand Down Expand Up @@ -691,7 +740,7 @@ construct_runtime!(
XTokens: orml_xtokens::{Pallet, Call, Storage, Event<T>},
AssetManager: pallet_asset_manager::{Pallet, Call, Storage, Event<T>},
XcmTransactor: xcm_transactor::{Pallet, Call, Storage, Event<T>},

Treasury: pallet_treasury::{Pallet, Storage, Config, Event<T>, Call}
}
);

Expand Down
74 changes: 74 additions & 0 deletions runtime/moonbase/tests/xcm_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,80 @@ fn receive_relay_asset_with_trader() {
ParaA::execute_with(|| {
// non-free execution, not full amount received
assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 90);
// Fee should have been received by treasury
assert_eq!(Assets::balance(source_id, &Treasury::account_id()), 10);
});
}

#[test]
fn send_para_a_asset_to_para_b_with_trader() {
MockNet::reset();

let para_a_balances = MultiLocation::new(1, X2(Parachain(1), PalletInstance(1u8)));
let source_location = parachain::AssetType::Xcm(para_a_balances);
let source_id: parachain::AssetId = source_location.clone().into();

let asset_metadata = parachain::AssetMetadata {
name: b"ParaAToken".to_vec(),
symbol: b"ParaA".to_vec(),
decimals: 18,
};

ParaB::execute_with(|| {
assert_ok!(AssetManager::register_asset(
parachain::Origin::root(),
source_location,
asset_metadata,
1u128,
));
assert_ok!(AssetManager::set_asset_units_per_second(
parachain::Origin::root(),
source_id,
2500000000000u128
));
});

let dest = MultiLocation {
parents: 1,
interior: X2(
Parachain(2),
AccountKey20 {
network: NetworkId::Any,
key: PARAALICE.into(),
},
),
};

// In destination chain, we only need 4 weight
// We put 10 weight, 6 of which should be refunded and 4 of which should go to treasury
ParaA::execute_with(|| {
assert_ok!(XTokens::transfer(
parachain::Origin::signed(PARAALICE.into()),
parachain::CurrencyId::SelfReserve,
100,
Box::new(VersionedMultiLocation::V1(dest)),
10
));
});
ParaA::execute_with(|| {
// free execution, full amount received
assert_eq!(
ParaBalances::free_balance(&PARAALICE.into()),
INITIAL_BALANCE - 100
);
});

// We are sending 100 tokens from para A.
// Amount spent in fees is Units per second * weight / 1_000_000_000_000 (weight per second)
// weight is 4 since we are executing 4 instructions with a unitweightcost of 1.
// Units per second should be 2_500_000_000_000_000
// Since we set 10 weight in destination chain, 25 will be charged upfront
// 15 of those will be refunded, while 10 will go to treasury as the true weight used
// will be 4
ParaB::execute_with(|| {
assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 90);
// Fee should have been received by treasury
assert_eq!(Assets::balance(source_id, &Treasury::account_id()), 10);
});
}

Expand Down
Loading