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

[stable2049] Backport #5546 #5710

Merged
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 13 additions & 16 deletions bridges/snowbridge/pallets/inbound-queue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,11 @@ use frame_support::{
};
use frame_system::ensure_signed;
use scale_info::TypeInfo;
use sp_core::{H160, H256};
use sp_core::H160;
use sp_runtime::traits::Zero;
use sp_std::vec;
use xcm::prelude::{
send_xcm, Instruction::SetTopic, Junction::*, Location, SendError as XcmpSendError, SendXcm,
Xcm, XcmContext, XcmHash,
send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash,
};
use xcm_executor::traits::TransactAsset;

Expand All @@ -62,9 +61,8 @@ use snowbridge_core::{
sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters,
StaticLookup,
};
use snowbridge_router_primitives::{
inbound,
inbound::{ConvertMessage, ConvertMessageError},
use snowbridge_router_primitives::inbound::{
ConvertMessage, ConvertMessageError, VersionedMessage,
};
use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError};

Expand All @@ -86,6 +84,7 @@ pub mod pallet {

use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_core::H256;

#[pallet::pallet]
pub struct Pallet<T>(_);
Expand Down Expand Up @@ -276,12 +275,12 @@ pub mod pallet {
T::Token::transfer(&sovereign_account, &who, amount, Preservation::Preserve)?;
}

// Decode payload into `VersionedMessage`
let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref())
.map_err(|_| Error::<T>::InvalidPayload)?;

// Decode message into XCM
let (xcm, fee) =
match inbound::VersionedMessage::decode_all(&mut envelope.payload.as_ref()) {
Ok(message) => Self::do_convert(envelope.message_id, message)?,
Err(_) => return Err(Error::<T>::InvalidPayload.into()),
};
let (xcm, fee) = Self::do_convert(envelope.message_id, message.clone())?;

log::info!(
target: LOG_TARGET,
Expand Down Expand Up @@ -323,12 +322,10 @@ pub mod pallet {
impl<T: Config> Pallet<T> {
pub fn do_convert(
message_id: H256,
message: inbound::VersionedMessage,
message: VersionedMessage,
) -> Result<(Xcm<()>, BalanceOf<T>), Error<T>> {
let (mut xcm, fee) =
T::MessageConverter::convert(message).map_err(|e| Error::<T>::ConvertMessage(e))?;
// Append the message id as an XCM topic
xcm.inner_mut().extend(vec![SetTopic(message_id.into())]);
let (xcm, fee) = T::MessageConverter::convert(message_id, message)
.map_err(|e| Error::<T>::ConvertMessage(e))?;
Ok((xcm, fee))
}

Expand Down
20 changes: 18 additions & 2 deletions bridges/snowbridge/pallets/inbound-queue/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ use snowbridge_beacon_primitives::{
use snowbridge_core::{
gwei,
inbound::{Log, Proof, VerificationError},
meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup,
meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId,
};
use snowbridge_router_primitives::inbound::MessageToXcm;
use sp_core::{H160, H256};
use sp_runtime::{
traits::{IdentifyAccount, IdentityLookup, Verify},
traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify},
BuildStorage, FixedU128, MultiSignature,
};
use sp_std::{convert::From, default::Default};
Expand Down Expand Up @@ -112,6 +112,9 @@ parameter_types! {
pub const SendTokenExecutionFee: u128 = 1_000_000_000;
pub const InitialFund: u128 = 1_000_000_000_000;
pub const InboundQueuePalletInstance: u8 = 80;
pub UniversalLocation: InteriorLocation =
[GlobalConsensus(Westend), Parachain(1002)].into();
pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(Westend),Parachain(1000)]);
}

#[cfg(feature = "runtime-benchmarks")]
Expand Down Expand Up @@ -205,6 +208,16 @@ impl TransactAsset for SuccessfulTransactor {
}
}

pub struct MockTokenIdConvert;
impl MaybeEquivalence<TokenId, Location> for MockTokenIdConvert {
fn convert(_id: &TokenId) -> Option<Location> {
Some(Location::parent())
}
fn convert_back(_loc: &Location) -> Option<TokenId> {
None
}
}

impl inbound_queue::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Verifier = MockVerifier;
Expand All @@ -218,6 +231,9 @@ impl inbound_queue::Config for Test {
InboundQueuePalletInstance,
AccountId,
Balance,
MockTokenIdConvert,
UniversalLocation,
AssetHubFromEthereum,
>;
type PricingParameters = Parameters;
type ChannelLookup = MockChannelLookup;
Expand Down
10 changes: 4 additions & 6 deletions bridges/snowbridge/pallets/outbound-queue/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,11 @@ pub fn mock_message(sibling_para_id: u32) -> Message {
Message {
id: None,
channel_id: ParaId::from(sibling_para_id).into(),
command: Command::AgentExecute {
command: Command::TransferNativeToken {
agent_id: Default::default(),
command: AgentExecuteCommand::TransferToken {
token: Default::default(),
recipient: Default::default(),
amount: 0,
},
token: Default::default(),
recipient: Default::default(),
amount: 0,
},
}
}
23 changes: 23 additions & 0 deletions bridges/snowbridge/pallets/system/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,29 @@ mod benchmarks {
Ok(())
}

#[benchmark]
fn register_token() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();

let amount: BalanceOf<T> =
(10_000_000_000_000_u128).saturated_into::<u128>().saturated_into();

T::Token::mint_into(&caller, amount)?;

let relay_token_asset_id: Location = Location::parent();
let asset = Box::new(VersionedLocation::V4(relay_token_asset_id));
let asset_metadata = AssetMetadata {
name: "wnd".as_bytes().to_vec().try_into().unwrap(),
symbol: "wnd".as_bytes().to_vec().try_into().unwrap(),
decimals: 12,
};

#[extrinsic_call]
_(RawOrigin::Root, asset, asset_metadata);

Ok(())
}

impl_benchmark_test_suite!(
SnowbridgeControl,
crate::mock::new_test_ext(true),
Expand Down
116 changes: 111 additions & 5 deletions bridges/snowbridge/pallets/system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@
//!
//! Typically, Polkadot governance will use the `force_transfer_native_from_agent` and
//! `force_update_channel` and extrinsics to manage agents and channels for system parachains.
//!
//! ## Polkadot-native tokens on Ethereum
//!
//! Tokens deposited on AssetHub pallet can be bridged to Ethereum as wrapped ERC20 tokens. As a
//! prerequisite, the token should be registered first.
//!
//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum.
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(test)]
mod mock;

Expand All @@ -63,13 +69,16 @@ use frame_system::pallet_prelude::*;
use snowbridge_core::{
meth,
outbound::{Command, Initializer, Message, OperatingMode, SendError, SendMessage},
sibling_sovereign_account, AgentId, Channel, ChannelId, ParaId,
PricingParameters as PricingParametersRecord, PRIMARY_GOVERNANCE_CHANNEL,
sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId,
PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL,
SECONDARY_GOVERNANCE_CHANNEL,
};
use sp_core::{RuntimeDebug, H160, H256};
use sp_io::hashing::blake2_256;
use sp_runtime::{traits::BadOrigin, DispatchError, SaturatedConversion};
use sp_runtime::{
traits::{BadOrigin, MaybeEquivalence},
DispatchError, SaturatedConversion,
};
use sp_std::prelude::*;
use xcm::prelude::*;
use xcm_executor::traits::ConvertLocation;
Expand Down Expand Up @@ -99,7 +108,7 @@ where
}

/// Hash the location to produce an agent id
fn agent_id_of<T: Config>(location: &Location) -> Result<H256, DispatchError> {
pub fn agent_id_of<T: Config>(location: &Location) -> Result<H256, DispatchError> {
T::AgentIdOf::convert_location(location).ok_or(Error::<T>::LocationConversionFailed.into())
}

Expand Down Expand Up @@ -127,6 +136,7 @@ where

#[frame_support::pallet]
pub mod pallet {
use frame_support::dispatch::PostDispatchInfo;
use snowbridge_core::StaticLookup;
use sp_core::U256;

Expand Down Expand Up @@ -164,6 +174,12 @@ pub mod pallet {

type WeightInfo: WeightInfo;

/// This chain's Universal Location.
type UniversalLocation: Get<InteriorLocation>;

// The bridges configured Ethereum location
type EthereumLocation: Get<Location>;

#[cfg(feature = "runtime-benchmarks")]
type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
}
Expand Down Expand Up @@ -211,6 +227,13 @@ pub mod pallet {
PricingParametersChanged {
params: PricingParametersOf<T>,
},
/// Register Polkadot-native token as a wrapped ERC20 token on Ethereum
RegisterToken {
/// Location of Polkadot-native token
location: VersionedLocation,
/// ID of Polkadot-native token on Ethereum
foreign_token_id: H256,
},
}

#[pallet::error]
Expand Down Expand Up @@ -243,6 +266,16 @@ pub mod pallet {
pub type PricingParameters<T: Config> =
StorageValue<_, PricingParametersOf<T>, ValueQuery, T::DefaultPricingParameters>;

/// Lookup table for foreign token ID to native location relative to ethereum
#[pallet::storage]
pub type ForeignToNativeId<T: Config> =
StorageMap<_, Blake2_128Concat, TokenId, xcm::v4::Location, OptionQuery>;

/// Lookup table for native location relative to ethereum to foreign token ID
#[pallet::storage]
pub type NativeToForeignId<T: Config> =
StorageMap<_, Blake2_128Concat, xcm::v4::Location, TokenId, OptionQuery>;

#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
Expand Down Expand Up @@ -574,6 +607,34 @@ pub mod pallet {
});
Ok(())
}

/// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum.
/// Privileged. Can only be called by root.
///
/// Fee required: No
///
/// - `origin`: Must be root
/// - `location`: Location of the asset (relative to this chain)
/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum
#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::register_token())]
pub fn register_token(
origin: OriginFor<T>,
location: Box<VersionedLocation>,
metadata: AssetMetadata,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;

let location: Location =
(*location).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;

Self::do_register_token(&location, metadata, PaysFee::<T>::No)?;

Ok(PostDispatchInfo {
actual_weight: Some(T::WeightInfo::register_token()),
pays_fee: Pays::No,
})
}
}

impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -663,6 +724,42 @@ pub mod pallet {
let secondary_exists = Channels::<T>::contains_key(SECONDARY_GOVERNANCE_CHANNEL);
primary_exists && secondary_exists
}

pub(crate) fn do_register_token(
location: &Location,
metadata: AssetMetadata,
pays_fee: PaysFee<T>,
) -> Result<(), DispatchError> {
let ethereum_location = T::EthereumLocation::get();
// reanchor to Ethereum context
let location = location
.clone()
.reanchored(&ethereum_location, &T::UniversalLocation::get())
.map_err(|_| Error::<T>::LocationConversionFailed)?;

let token_id = TokenIdOf::convert_location(&location)
.ok_or(Error::<T>::LocationConversionFailed)?;

if !ForeignToNativeId::<T>::contains_key(token_id) {
NativeToForeignId::<T>::insert(location.clone(), token_id);
ForeignToNativeId::<T>::insert(token_id, location.clone());
}

let command = Command::RegisterForeignToken {
token_id,
name: metadata.name.into_inner(),
symbol: metadata.symbol.into_inner(),
decimals: metadata.decimals,
};
Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;

Self::deposit_event(Event::<T>::RegisterToken {
location: location.clone().into(),
foreign_token_id: token_id,
});

Ok(())
}
}

impl<T: Config> StaticLookup for Pallet<T> {
Expand All @@ -684,4 +781,13 @@ pub mod pallet {
PricingParameters::<T>::get()
}
}

impl<T: Config> MaybeEquivalence<TokenId, Location> for Pallet<T> {
fn convert(foreign_id: &TokenId) -> Option<Location> {
ForeignToNativeId::<T>::get(foreign_id)
}
fn convert_back(location: &Location) -> Option<TokenId> {
NativeToForeignId::<T>::get(location)
}
}
}
Loading
Loading