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

Commit

Permalink
Polkadot assets on ethereum (#128)
Browse files Browse the repository at this point in the history
* Register token from polkadot

* Extract AssetRegistrarMetadata

* ReserveTransfer from AssetHub

* Transfer DOT back to AssetHub

* Fix breaking tests

* Fix register token

* Increase dispatch_gas to cover the actual cost for register token

* Add ConvertAssetId to outbound router

* Rename to SendForeignToken

* Update cost

* Move Command.RegisterToken to top level

* Use VersionedLocation for storage

* Use versioned location

* TokenIdOf follow the same pattern as AgentIdOf

* Rename MintToken to TransferNativeToken

* Rename as SendNativeToken

* More refactoring

* Remove AgentExecuteCommand

* Remove TokenExists check

* Update cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs

Co-authored-by: Clara van Staden <[email protected]>

* Benchmark register_token

* More checks for the events

* More tests for send native token

* More tests describe tokenID

* More asset identifiers

* Add LocationToToken & Always use VersionedLocation

* Fix breaking tests

* Update gas cost for registering polkadot token

* Add AgentExecuteCommand back for compatibility

* Split into 2 tests each covers one direction

* Revert the change on AH

* Switch to native token on penpal for the integration

* Make fee asset as configuration parameter of the Channel

* Revert fee_asset_id as channel property

* Short epoch

* Fix integration tests

* Fix format

* Cleanup

* Register relay token

* Use relay token as fee asset

* Fix clippy

* Fix missing dependency

* Remove reanchored prefix from asset_id

* Fix test

* Fix the instruction

* Fix test

* Multi hop transfer

* Fix tests

* allow bridge hub assets

* Register token from BH directly and remove force_register_token

* Decrease gas estimation for PNA

* Cleanup

* Store Location rather than VersionedLocation

* Fix test

* Add more tests

* Revamp reanchor logic

* Improve reanchor & Fix tests

* Use secondary governance channel to register PNA

* Rename as asset location

* Use BoundVec limit size of name&symbol

* Describe location of PNA & more tests

* Add test

* Fix taplo

* More tests

* Cleanup

* Format code

* Batch rename token command

* Remove agent_id

* Rename as AssetMetadata

* Add test for penpal native token

---------

Co-authored-by: Clara van Staden <[email protected]>
Co-authored-by: Alistair Singh <[email protected]>
  • Loading branch information
3 people authored Sep 2, 2024
1 parent dfee885 commit b19c1e1
Show file tree
Hide file tree
Showing 27 changed files with 1,640 additions and 253 deletions.
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)]);
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>;

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

#[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)?;

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

0 comments on commit b19c1e1

Please sign in to comment.