Skip to content

Commit c1b1394

Browse files
vgeddesyrongclaravanstadenalistair-singhfranciscoaguirre
authored
[stable2049] Backport #5546 (#5710)
Co-authored-by: Ron <[email protected]> Co-authored-by: Clara van Staden <[email protected]> Co-authored-by: Alistair Singh <[email protected]> Co-authored-by: Francisco Aguirre <[email protected]> Co-authored-by: Adrian Catangiu <[email protected]>
1 parent 9330fc3 commit c1b1394

File tree

29 files changed

+1626
-298
lines changed

29 files changed

+1626
-298
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bridges/snowbridge/pallets/inbound-queue/src/lib.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,11 @@ use frame_support::{
4848
};
4949
use frame_system::ensure_signed;
5050
use scale_info::TypeInfo;
51-
use sp_core::{H160, H256};
51+
use sp_core::H160;
5252
use sp_runtime::traits::Zero;
5353
use sp_std::vec;
5454
use xcm::prelude::{
55-
send_xcm, Instruction::SetTopic, Junction::*, Location, SendError as XcmpSendError, SendXcm,
56-
Xcm, XcmContext, XcmHash,
55+
send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash,
5756
};
5857
use xcm_executor::traits::TransactAsset;
5958

@@ -62,9 +61,8 @@ use snowbridge_core::{
6261
sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters,
6362
StaticLookup,
6463
};
65-
use snowbridge_router_primitives::{
66-
inbound,
67-
inbound::{ConvertMessage, ConvertMessageError},
64+
use snowbridge_router_primitives::inbound::{
65+
ConvertMessage, ConvertMessageError, VersionedMessage,
6866
};
6967
use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError};
7068

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

8785
use frame_support::pallet_prelude::*;
8886
use frame_system::pallet_prelude::*;
87+
use sp_core::H256;
8988

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

278+
// Decode payload into `VersionedMessage`
279+
let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref())
280+
.map_err(|_| Error::<T>::InvalidPayload)?;
281+
279282
// Decode message into XCM
280-
let (xcm, fee) =
281-
match inbound::VersionedMessage::decode_all(&mut envelope.payload.as_ref()) {
282-
Ok(message) => Self::do_convert(envelope.message_id, message)?,
283-
Err(_) => return Err(Error::<T>::InvalidPayload.into()),
284-
};
283+
let (xcm, fee) = Self::do_convert(envelope.message_id, message.clone())?;
285284

286285
log::info!(
287286
target: LOG_TARGET,
@@ -323,12 +322,10 @@ pub mod pallet {
323322
impl<T: Config> Pallet<T> {
324323
pub fn do_convert(
325324
message_id: H256,
326-
message: inbound::VersionedMessage,
325+
message: VersionedMessage,
327326
) -> Result<(Xcm<()>, BalanceOf<T>), Error<T>> {
328-
let (mut xcm, fee) =
329-
T::MessageConverter::convert(message).map_err(|e| Error::<T>::ConvertMessage(e))?;
330-
// Append the message id as an XCM topic
331-
xcm.inner_mut().extend(vec![SetTopic(message_id.into())]);
327+
let (xcm, fee) = T::MessageConverter::convert(message_id, message)
328+
.map_err(|e| Error::<T>::ConvertMessage(e))?;
332329
Ok((xcm, fee))
333330
}
334331

bridges/snowbridge/pallets/inbound-queue/src/mock.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ use snowbridge_beacon_primitives::{
1010
use snowbridge_core::{
1111
gwei,
1212
inbound::{Log, Proof, VerificationError},
13-
meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup,
13+
meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId,
1414
};
1515
use snowbridge_router_primitives::inbound::MessageToXcm;
1616
use sp_core::{H160, H256};
1717
use sp_runtime::{
18-
traits::{IdentifyAccount, IdentityLookup, Verify},
18+
traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify},
1919
BuildStorage, FixedU128, MultiSignature,
2020
};
2121
use sp_std::{convert::From, default::Default};
@@ -112,6 +112,9 @@ parameter_types! {
112112
pub const SendTokenExecutionFee: u128 = 1_000_000_000;
113113
pub const InitialFund: u128 = 1_000_000_000_000;
114114
pub const InboundQueuePalletInstance: u8 = 80;
115+
pub UniversalLocation: InteriorLocation =
116+
[GlobalConsensus(Westend), Parachain(1002)].into();
117+
pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(Westend),Parachain(1000)]);
115118
}
116119

117120
#[cfg(feature = "runtime-benchmarks")]
@@ -205,6 +208,16 @@ impl TransactAsset for SuccessfulTransactor {
205208
}
206209
}
207210

211+
pub struct MockTokenIdConvert;
212+
impl MaybeEquivalence<TokenId, Location> for MockTokenIdConvert {
213+
fn convert(_id: &TokenId) -> Option<Location> {
214+
Some(Location::parent())
215+
}
216+
fn convert_back(_loc: &Location) -> Option<TokenId> {
217+
None
218+
}
219+
}
220+
208221
impl inbound_queue::Config for Test {
209222
type RuntimeEvent = RuntimeEvent;
210223
type Verifier = MockVerifier;
@@ -218,6 +231,9 @@ impl inbound_queue::Config for Test {
218231
InboundQueuePalletInstance,
219232
AccountId,
220233
Balance,
234+
MockTokenIdConvert,
235+
UniversalLocation,
236+
AssetHubFromEthereum,
221237
>;
222238
type PricingParameters = Parameters;
223239
type ChannelLookup = MockChannelLookup;

bridges/snowbridge/pallets/outbound-queue/src/mock.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,11 @@ pub fn mock_message(sibling_para_id: u32) -> Message {
164164
Message {
165165
id: None,
166166
channel_id: ParaId::from(sibling_para_id).into(),
167-
command: Command::AgentExecute {
167+
command: Command::TransferNativeToken {
168168
agent_id: Default::default(),
169-
command: AgentExecuteCommand::TransferToken {
170-
token: Default::default(),
171-
recipient: Default::default(),
172-
amount: 0,
173-
},
169+
token: Default::default(),
170+
recipient: Default::default(),
171+
amount: 0,
174172
},
175173
}
176174
}

bridges/snowbridge/pallets/system/src/benchmarking.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,29 @@ mod benchmarks {
159159
Ok(())
160160
}
161161

162+
#[benchmark]
163+
fn register_token() -> Result<(), BenchmarkError> {
164+
let caller: T::AccountId = whitelisted_caller();
165+
166+
let amount: BalanceOf<T> =
167+
(10_000_000_000_000_u128).saturated_into::<u128>().saturated_into();
168+
169+
T::Token::mint_into(&caller, amount)?;
170+
171+
let relay_token_asset_id: Location = Location::parent();
172+
let asset = Box::new(VersionedLocation::V4(relay_token_asset_id));
173+
let asset_metadata = AssetMetadata {
174+
name: "wnd".as_bytes().to_vec().try_into().unwrap(),
175+
symbol: "wnd".as_bytes().to_vec().try_into().unwrap(),
176+
decimals: 12,
177+
};
178+
179+
#[extrinsic_call]
180+
_(RawOrigin::Root, asset, asset_metadata);
181+
182+
Ok(())
183+
}
184+
162185
impl_benchmark_test_suite!(
163186
SnowbridgeControl,
164187
crate::mock::new_test_ext(true),

bridges/snowbridge/pallets/system/src/lib.rs

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@
3535
//!
3636
//! Typically, Polkadot governance will use the `force_transfer_native_from_agent` and
3737
//! `force_update_channel` and extrinsics to manage agents and channels for system parachains.
38+
//!
39+
//! ## Polkadot-native tokens on Ethereum
40+
//!
41+
//! Tokens deposited on AssetHub pallet can be bridged to Ethereum as wrapped ERC20 tokens. As a
42+
//! prerequisite, the token should be registered first.
43+
//!
44+
//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum.
3845
#![cfg_attr(not(feature = "std"), no_std)]
39-
4046
#[cfg(test)]
4147
mod mock;
4248

@@ -63,13 +69,16 @@ use frame_system::pallet_prelude::*;
6369
use snowbridge_core::{
6470
meth,
6571
outbound::{Command, Initializer, Message, OperatingMode, SendError, SendMessage},
66-
sibling_sovereign_account, AgentId, Channel, ChannelId, ParaId,
67-
PricingParameters as PricingParametersRecord, PRIMARY_GOVERNANCE_CHANNEL,
72+
sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId,
73+
PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL,
6874
SECONDARY_GOVERNANCE_CHANNEL,
6975
};
7076
use sp_core::{RuntimeDebug, H160, H256};
7177
use sp_io::hashing::blake2_256;
72-
use sp_runtime::{traits::BadOrigin, DispatchError, SaturatedConversion};
78+
use sp_runtime::{
79+
traits::{BadOrigin, MaybeEquivalence},
80+
DispatchError, SaturatedConversion,
81+
};
7382
use sp_std::prelude::*;
7483
use xcm::prelude::*;
7584
use xcm_executor::traits::ConvertLocation;
@@ -99,7 +108,7 @@ where
99108
}
100109

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

@@ -127,6 +136,7 @@ where
127136

128137
#[frame_support::pallet]
129138
pub mod pallet {
139+
use frame_support::dispatch::PostDispatchInfo;
130140
use snowbridge_core::StaticLookup;
131141
use sp_core::U256;
132142

@@ -164,6 +174,12 @@ pub mod pallet {
164174

165175
type WeightInfo: WeightInfo;
166176

177+
/// This chain's Universal Location.
178+
type UniversalLocation: Get<InteriorLocation>;
179+
180+
// The bridges configured Ethereum location
181+
type EthereumLocation: Get<Location>;
182+
167183
#[cfg(feature = "runtime-benchmarks")]
168184
type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
169185
}
@@ -211,6 +227,13 @@ pub mod pallet {
211227
PricingParametersChanged {
212228
params: PricingParametersOf<T>,
213229
},
230+
/// Register Polkadot-native token as a wrapped ERC20 token on Ethereum
231+
RegisterToken {
232+
/// Location of Polkadot-native token
233+
location: VersionedLocation,
234+
/// ID of Polkadot-native token on Ethereum
235+
foreign_token_id: H256,
236+
},
214237
}
215238

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

269+
/// Lookup table for foreign token ID to native location relative to ethereum
270+
#[pallet::storage]
271+
pub type ForeignToNativeId<T: Config> =
272+
StorageMap<_, Blake2_128Concat, TokenId, xcm::v4::Location, OptionQuery>;
273+
274+
/// Lookup table for native location relative to ethereum to foreign token ID
275+
#[pallet::storage]
276+
pub type NativeToForeignId<T: Config> =
277+
StorageMap<_, Blake2_128Concat, xcm::v4::Location, TokenId, OptionQuery>;
278+
246279
#[pallet::genesis_config]
247280
#[derive(frame_support::DefaultNoBound)]
248281
pub struct GenesisConfig<T: Config> {
@@ -574,6 +607,34 @@ pub mod pallet {
574607
});
575608
Ok(())
576609
}
610+
611+
/// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum.
612+
/// Privileged. Can only be called by root.
613+
///
614+
/// Fee required: No
615+
///
616+
/// - `origin`: Must be root
617+
/// - `location`: Location of the asset (relative to this chain)
618+
/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum
619+
#[pallet::call_index(10)]
620+
#[pallet::weight(T::WeightInfo::register_token())]
621+
pub fn register_token(
622+
origin: OriginFor<T>,
623+
location: Box<VersionedLocation>,
624+
metadata: AssetMetadata,
625+
) -> DispatchResultWithPostInfo {
626+
ensure_root(origin)?;
627+
628+
let location: Location =
629+
(*location).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
630+
631+
Self::do_register_token(&location, metadata, PaysFee::<T>::No)?;
632+
633+
Ok(PostDispatchInfo {
634+
actual_weight: Some(T::WeightInfo::register_token()),
635+
pays_fee: Pays::No,
636+
})
637+
}
577638
}
578639

579640
impl<T: Config> Pallet<T> {
@@ -663,6 +724,42 @@ pub mod pallet {
663724
let secondary_exists = Channels::<T>::contains_key(SECONDARY_GOVERNANCE_CHANNEL);
664725
primary_exists && secondary_exists
665726
}
727+
728+
pub(crate) fn do_register_token(
729+
location: &Location,
730+
metadata: AssetMetadata,
731+
pays_fee: PaysFee<T>,
732+
) -> Result<(), DispatchError> {
733+
let ethereum_location = T::EthereumLocation::get();
734+
// reanchor to Ethereum context
735+
let location = location
736+
.clone()
737+
.reanchored(&ethereum_location, &T::UniversalLocation::get())
738+
.map_err(|_| Error::<T>::LocationConversionFailed)?;
739+
740+
let token_id = TokenIdOf::convert_location(&location)
741+
.ok_or(Error::<T>::LocationConversionFailed)?;
742+
743+
if !ForeignToNativeId::<T>::contains_key(token_id) {
744+
NativeToForeignId::<T>::insert(location.clone(), token_id);
745+
ForeignToNativeId::<T>::insert(token_id, location.clone());
746+
}
747+
748+
let command = Command::RegisterForeignToken {
749+
token_id,
750+
name: metadata.name.into_inner(),
751+
symbol: metadata.symbol.into_inner(),
752+
decimals: metadata.decimals,
753+
};
754+
Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
755+
756+
Self::deposit_event(Event::<T>::RegisterToken {
757+
location: location.clone().into(),
758+
foreign_token_id: token_id,
759+
});
760+
761+
Ok(())
762+
}
666763
}
667764

668765
impl<T: Config> StaticLookup for Pallet<T> {
@@ -684,4 +781,13 @@ pub mod pallet {
684781
PricingParameters::<T>::get()
685782
}
686783
}
784+
785+
impl<T: Config> MaybeEquivalence<TokenId, Location> for Pallet<T> {
786+
fn convert(foreign_id: &TokenId) -> Option<Location> {
787+
ForeignToNativeId::<T>::get(foreign_id)
788+
}
789+
fn convert_back(location: &Location) -> Option<TokenId> {
790+
NativeToForeignId::<T>::get(location)
791+
}
792+
}
687793
}

0 commit comments

Comments
 (0)