Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Polkadot assets on ethereum #128

Merged
merged 77 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
6019c8d
Register token from polkadot
yrong Mar 21, 2024
c613fc6
Extract AssetRegistrarMetadata
yrong Mar 21, 2024
9672b52
ReserveTransfer from AssetHub
yrong Mar 22, 2024
c017bfe
Transfer DOT back to AssetHub
yrong Mar 22, 2024
3dc37ea
Fix breaking tests
yrong Mar 22, 2024
1f00024
Fix register token
yrong Mar 25, 2024
a20a8b7
Increase dispatch_gas to cover the actual cost for register token
yrong Mar 26, 2024
41104d5
Add ConvertAssetId to outbound router
yrong Mar 26, 2024
a8b006b
Rename to SendForeignToken
yrong Mar 27, 2024
b8a75d1
Update cost
yrong Mar 29, 2024
fec5a63
Move Command.RegisterToken to top level
yrong Apr 4, 2024
a683680
Use VersionedLocation for storage
yrong Apr 4, 2024
3621de4
Use versioned location
yrong Apr 4, 2024
f7ed096
TokenIdOf follow the same pattern as AgentIdOf
yrong Apr 5, 2024
36bb656
Rename MintToken to TransferNativeToken
yrong Apr 5, 2024
cf091f6
Rename as SendNativeToken
yrong Apr 5, 2024
4921280
Merge branch 'snowbridge' into ron/polkadot-assets-on-ethereum
yrong Apr 5, 2024
10c5e10
More refactoring
yrong Apr 5, 2024
587fb6e
Remove AgentExecuteCommand
yrong Apr 9, 2024
f56567d
Remove TokenExists check
yrong Apr 10, 2024
c792204
Update cumulus/parachains/integration-tests/emulated/tests/bridges/br…
yrong Apr 10, 2024
a81b87f
Merge branch 'ron/polkadot-assets-on-ethereum' of https://github.com/…
yrong Apr 10, 2024
ce91a8c
Benchmark register_token
yrong Apr 10, 2024
f3c9d11
More checks for the events
yrong Apr 10, 2024
41aa563
More tests for send native token
yrong Apr 10, 2024
d6c6330
More tests describe tokenID
yrong Apr 10, 2024
faf8f73
More asset identifiers
yrong Apr 10, 2024
7001866
Add LocationToToken & Always use VersionedLocation
yrong Apr 10, 2024
1edad16
Fix breaking tests
yrong Apr 10, 2024
933130a
Update gas cost for registering polkadot token
yrong Apr 10, 2024
8ace79e
Merge branch 'bridge-next-gen' into ron/polkadot-assets-on-ethereum
yrong Apr 11, 2024
40cc470
Add AgentExecuteCommand back for compatibility
yrong Apr 11, 2024
2d8f3b1
Merge branch 'bridge-next-gen' into ron/polkadot-assets-on-ethereum
yrong Apr 16, 2024
5a4f3af
Split into 2 tests each covers one direction
yrong Apr 16, 2024
50983ed
Revert the change on AH
yrong Apr 19, 2024
5ad44df
Switch to native token on penpal for the integration
yrong Apr 19, 2024
8a79d89
Make fee asset as configuration parameter of the Channel
yrong Apr 26, 2024
0251493
Revert fee_asset_id as channel property
yrong May 7, 2024
fa70f7b
Short epoch
yrong Aug 21, 2024
3d0fae3
Merge branch 'snowbridge' into ron/polkadot-assets-on-ethereum
yrong Aug 22, 2024
25c819a
Fix integration tests
yrong Aug 22, 2024
3cb2f05
Fix format
yrong Aug 22, 2024
3a94733
Cleanup
yrong Aug 22, 2024
9d900b2
Register relay token
yrong Aug 22, 2024
33ea85e
Use relay token as fee asset
yrong Aug 22, 2024
d64e783
Fix clippy
yrong Aug 22, 2024
79fb3b5
Fix missing dependency
yrong Aug 23, 2024
8609eed
Remove reanchored prefix from asset_id
yrong Aug 26, 2024
3f1ded3
Fix test
yrong Aug 26, 2024
0e06cae
Fix the instruction
yrong Aug 26, 2024
4a04f1f
Fix test
yrong Aug 26, 2024
0d3988c
Multi hop transfer
yrong Aug 27, 2024
f1155b1
Fix tests
yrong Aug 27, 2024
a44db43
allow bridge hub assets
alistair-singh Aug 28, 2024
0f0fd8e
Register token from BH directly and remove force_register_token
yrong Aug 28, 2024
f5c6b92
Decrease gas estimation for PNA
yrong Aug 28, 2024
1095bff
Cleanup
yrong Aug 28, 2024
21fa444
Merge branch 'ron/short-epoch' into ron/polkadot-assets-on-ethereum
yrong Aug 28, 2024
2675241
Store Location rather than VersionedLocation
yrong Aug 29, 2024
42933da
Fix test
yrong Aug 29, 2024
760d697
Merge branch 'snowbridge' into ron/polkadot-assets-on-ethereum
yrong Aug 29, 2024
a550c42
Add more tests
yrong Aug 29, 2024
736fe90
Revamp reanchor logic
yrong Aug 29, 2024
d1b8d16
Improve reanchor & Fix tests
yrong Aug 29, 2024
897fdad
Use secondary governance channel to register PNA
yrong Aug 29, 2024
e733321
Rename as asset location
yrong Aug 29, 2024
31c75e9
Use BoundVec limit size of name&symbol
yrong Aug 29, 2024
009402e
Describe location of PNA & more tests
yrong Aug 29, 2024
949fb45
Add test
yrong Aug 30, 2024
b38b20c
Fix taplo
yrong Aug 30, 2024
0b2f782
More tests
yrong Aug 30, 2024
62eef65
Cleanup
yrong Aug 30, 2024
39a84b9
Format code
yrong Aug 30, 2024
5ae8b0b
Batch rename token command
yrong Aug 30, 2024
6ca963d
Remove agent_id
yrong Aug 30, 2024
79e62fa
Rename as AssetMetadata
yrong Aug 30, 2024
8a4f582
Add test for penpal native token
yrong Aug 30, 2024
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.

28 changes: 17 additions & 11 deletions bridges/snowbridge/pallets/inbound-queue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,15 @@ 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};

pub use weights::WeightInfo;

#[cfg(feature = "runtime-benchmarks")]
use snowbridge_beacon_primitives::BeaconHeader;
#[cfg(feature = "runtime-benchmarks")]
use sp_core::H256;

type BalanceOf<T> =
<<T as pallet::Config>::Token as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
Expand All @@ -87,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 @@ -277,13 +275,12 @@ pub mod pallet {
T::Token::transfer(&sovereign_account, &who, amount, Preservation::Preserve)?;
}

// Decode payload into VersionMessage
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) => T::MessageConverter::convert(envelope.message_id, message)
.map_err(|e| Error::<T>::ConvertMessage(e))?,
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,6 +320,15 @@ pub mod pallet {
}

impl<T: Config> Pallet<T> {
pub fn do_convert(
message_id: H256,
message: VersionedMessage,
) -> Result<(Xcm<()>, BalanceOf<T>), Error<T>> {
let (xcm, fee) = T::MessageConverter::convert(message_id, message)
.map_err(|e| Error::<T>::ConvertMessage(e))?;
Ok((xcm, fee))
}

pub fn send_xcm(xcm: Xcm<()>, dest: ParaId) -> Result<XcmHash, Error<T>> {
let dest = Location::new(1, [Parachain(dest.into())]);
let (xcm_hash, _) = send_xcm::<T::XcmSender>(dest, xcm).map_err(Error::<T>::from)?;
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(1013)].into();
pub GlobalAssetHub: 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,
GlobalAssetHub,
>;
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::new(1, [GlobalConsensus(Westend)]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like we accepting this in Location relative to the Ethereum address.
I think we should accept an asset relative to bridge hub as input, and then re-achoring it onto the bridge location.

Copy link
Author

@yrong yrong Sep 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is the Location relative to the Ethereum after reanchored on AH in

On BH outbound router we check the asset location registered as expected in

let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?;
let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?;
ensure!(asset_id == expected_asset_id, InvalidAsset);

I prefer to register PNA with the reanchored location directly without adding reanchor logic in outbound router every time when receiving the xcm from AH.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am suggesting doing the re-anchoring once inside the registration extrinsic.

All other XCM related functions, when taking locations as input, are relative to the current chain. See pallet-xcm for example. So its not obvious to users that this is the required way to call it.

The other thing is that Locations are not distinct:
(2, [GlobalConsensus(Westend)]) and (1, [Here]) are the same asset and if re-anchoring from bridge hub will both collapse down to the unique (1, [GlobalConsensus(Westend)]). So they get the same id.

Copy link
Author

@yrong yrong Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other XCM related functions, when taking locations as input, are relative to the current chain

Yeah, that's true. But it's a bit different here actually the asset location is reanchored in pallet-xcm relative to Ethereum in the view of AH. Then routed to BH with SovereignPaidRemoteExporter without reanchoring.

TBH I don't think the reanchor in pallet-xcm is nessessary at all for our use case. But it's another story.

So I'd prefer to make things easy for now(Permissionless register token is not even enabled yet) and improve it later in another PR.

@alistair-singh WDYT?

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::Signed(caller), asset, asset_metadata);

Ok(())
}

impl_benchmark_test_suite!(
SnowbridgeControl,
crate::mock::new_test_ext(true),
Expand Down
82 changes: 78 additions & 4 deletions bridges/snowbridge/pallets/system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,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 +102,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 @@ -211,6 +214,11 @@ pub mod pallet {
PricingParametersChanged {
params: PricingParametersOf<T>,
},
/// Register token
RegisterToken {
asset_id: VersionedLocation,
token_id: H256,
},
}

#[pallet::error]
Expand All @@ -226,6 +234,7 @@ pub mod pallet {
InvalidTokenTransferFees,
InvalidPricingParameters,
InvalidUpgradeParameters,
TokenExists,
}

/// The set of registered agents
Expand All @@ -243,6 +252,15 @@ pub mod pallet {
pub type PricingParameters<T: Config> =
StorageValue<_, PricingParametersOf<T>, ValueQuery, T::DefaultPricingParameters>;

#[pallet::storage]
#[pallet::getter(fn tokens)]
pub type Tokens<T: Config> = StorageMap<_, Twox64Concat, TokenId, Location, OptionQuery>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. Location version is implicitly v4.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was rolled back.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alistair-singh We should not use VersionedLocation for fields in storage. There is other commentary in the PR about this.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is stopping someone from rolling over xcm versions prelude without us knowing and breaking the lookup of Tokens?

Copy link
Author

@yrong yrong Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With paritytech@5ef32c8 will just not compile.


#[pallet::storage]
#[pallet::getter(fn location_tokens)]
pub type LocationToToken<T: Config> =
StorageMap<_, Twox64Concat, Location, TokenId, OptionQuery>;
alistair-singh marked this conversation as resolved.
Show resolved Hide resolved

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

/// Sends a message to the Gateway contract to register a new
/// token that represents `asset`.
///
/// - `origin`: Must be `MultiLocation` from sibling parachain
#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::register_token())]
pub fn register_token(
origin: OriginFor<T>,
location: Box<VersionedLocation>,
metadata: AssetMetadata,
) -> DispatchResult {
let who = ensure_signed(origin)?;
alistair-singh marked this conversation as resolved.
Show resolved Hide resolved

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

let pays_fee = PaysFee::<T>::Yes(who);

Self::do_register_token(asset_loc, metadata, pays_fee)?;

Ok(())
}
}

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

pub(crate) fn do_register_token(
asset_loc: Location,
metadata: AssetMetadata,
pays_fee: PaysFee<T>,
) -> Result<(), DispatchError> {
// Record the token id or fail if it has already been created
let token_id = TokenIdOf::convert_location(&asset_loc)
.ok_or(Error::<T>::LocationConversionFailed)?;
Tokens::<T>::insert(token_id, asset_loc.clone());
LocationToToken::<T>::insert(asset_loc.clone(), token_id);

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 { asset_id: asset_loc.into(), token_id });

Ok(())
}
}

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

impl<T: Config> MaybeEquivalence<TokenId, Location> for Pallet<T> {
fn convert(id: &TokenId) -> Option<Location> {
Tokens::<T>::get(id)
}
fn convert_back(loc: &Location) -> Option<TokenId> {
LocationToToken::<T>::get(loc)
}
}
}
Loading
Loading