mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 13:21:01 +00:00
Removes Snowbridge parachain directory (#3186)
Removes the `bridges/snowbridge/parachain` directory and moves everything up to under `snowbridge` directly. We are cleaning up our local dev env after merging our crates into the polkadot-sdk. --------- Co-authored-by: claravanstaden <Cats 4 life!>
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Converts messages from Ethereum to XCM messages
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use core::marker::PhantomData;
|
||||
use frame_support::{traits::tokens::Balance as BalanceT, weights::Weight, PalletError};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::{Get, RuntimeDebug, H160};
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::MultiAddress;
|
||||
use sp_std::prelude::*;
|
||||
use xcm::prelude::{Junction::AccountKey20, *};
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
const MINIMUM_DEPOSIT: u128 = 1;
|
||||
|
||||
/// Messages from Ethereum are versioned. This is because in future,
|
||||
/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly.
|
||||
/// Instead having BridgeHub transcode the messages into XCM.
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug)]
|
||||
pub enum VersionedMessage {
|
||||
V1(MessageV1),
|
||||
}
|
||||
|
||||
/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are
|
||||
/// self-contained, in that they can be transcoded using only information in the message.
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug)]
|
||||
pub struct MessageV1 {
|
||||
/// EIP-155 chain id of the origin Ethereum network
|
||||
pub chain_id: u64,
|
||||
/// The command originating from the Gateway contract
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug)]
|
||||
pub enum Command {
|
||||
/// Register a wrapped token on the AssetHub `ForeignAssets` pallet
|
||||
RegisterToken {
|
||||
/// The address of the ERC20 token to be bridged over to AssetHub
|
||||
token: H160,
|
||||
/// XCM execution fee on AssetHub
|
||||
fee: u128,
|
||||
},
|
||||
/// Send a token to AssetHub or another parachain
|
||||
SendToken {
|
||||
/// The address of the ERC20 token to be bridged over to AssetHub
|
||||
token: H160,
|
||||
/// The destination for the transfer
|
||||
destination: Destination,
|
||||
/// Amount to transfer
|
||||
amount: u128,
|
||||
/// XCM execution fee on AssetHub
|
||||
fee: u128,
|
||||
},
|
||||
}
|
||||
|
||||
/// Destination for bridged tokens
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug)]
|
||||
pub enum Destination {
|
||||
/// The funds will be deposited into account `id` on AssetHub
|
||||
AccountId32 { id: [u8; 32] },
|
||||
/// The funds will deposited into the sovereign account of destination parachain `para_id` on
|
||||
/// AssetHub, Account `id` on the destination parachain will receive the funds via a
|
||||
/// reserve-backed transfer. See <https://github.com/paritytech/xcm-format#depositreserveasset>
|
||||
ForeignAccountId32 {
|
||||
para_id: u32,
|
||||
id: [u8; 32],
|
||||
/// XCM execution fee on final destination
|
||||
fee: u128,
|
||||
},
|
||||
/// The funds will deposited into the sovereign account of destination parachain `para_id` on
|
||||
/// AssetHub, Account `id` on the destination parachain will receive the funds via a
|
||||
/// reserve-backed transfer. See <https://github.com/paritytech/xcm-format#depositreserveasset>
|
||||
ForeignAccountId20 {
|
||||
para_id: u32,
|
||||
id: [u8; 20],
|
||||
/// XCM execution fee on final destination
|
||||
fee: u128,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct MessageToXcm<
|
||||
CreateAssetCall,
|
||||
CreateAssetDeposit,
|
||||
InboundQueuePalletInstance,
|
||||
AccountId,
|
||||
Balance,
|
||||
> where
|
||||
CreateAssetCall: Get<CallIndex>,
|
||||
CreateAssetDeposit: Get<u128>,
|
||||
Balance: BalanceT,
|
||||
{
|
||||
_phantom: PhantomData<(
|
||||
CreateAssetCall,
|
||||
CreateAssetDeposit,
|
||||
InboundQueuePalletInstance,
|
||||
AccountId,
|
||||
Balance,
|
||||
)>,
|
||||
}
|
||||
|
||||
/// Reason why a message conversion failed.
|
||||
#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)]
|
||||
pub enum ConvertMessageError {
|
||||
/// The message version is not supported for conversion.
|
||||
UnsupportedVersion,
|
||||
}
|
||||
|
||||
/// convert the inbound message to xcm which will be forwarded to the destination chain
|
||||
pub trait ConvertMessage {
|
||||
type Balance: BalanceT + From<u128>;
|
||||
type AccountId;
|
||||
/// Converts a versioned message into an XCM message and an optional topicID
|
||||
fn convert(message: VersionedMessage) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>;
|
||||
}
|
||||
|
||||
pub type CallIndex = [u8; 2];
|
||||
|
||||
impl<CreateAssetCall, CreateAssetDeposit, InboundQueuePalletInstance, AccountId, Balance>
|
||||
ConvertMessage
|
||||
for MessageToXcm<
|
||||
CreateAssetCall,
|
||||
CreateAssetDeposit,
|
||||
InboundQueuePalletInstance,
|
||||
AccountId,
|
||||
Balance,
|
||||
> where
|
||||
CreateAssetCall: Get<CallIndex>,
|
||||
CreateAssetDeposit: Get<u128>,
|
||||
InboundQueuePalletInstance: Get<u8>,
|
||||
Balance: BalanceT + From<u128>,
|
||||
AccountId: Into<[u8; 32]>,
|
||||
{
|
||||
type Balance = Balance;
|
||||
type AccountId = AccountId;
|
||||
|
||||
fn convert(message: VersionedMessage) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> {
|
||||
use Command::*;
|
||||
use VersionedMessage::*;
|
||||
match message {
|
||||
V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) =>
|
||||
Ok(Self::convert_register_token(chain_id, token, fee)),
|
||||
V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) =>
|
||||
Ok(Self::convert_send_token(chain_id, token, destination, amount, fee)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<CreateAssetCall, CreateAssetDeposit, InboundQueuePalletInstance, AccountId, Balance>
|
||||
MessageToXcm<CreateAssetCall, CreateAssetDeposit, InboundQueuePalletInstance, AccountId, Balance>
|
||||
where
|
||||
CreateAssetCall: Get<CallIndex>,
|
||||
CreateAssetDeposit: Get<u128>,
|
||||
InboundQueuePalletInstance: Get<u8>,
|
||||
Balance: BalanceT + From<u128>,
|
||||
AccountId: Into<[u8; 32]>,
|
||||
{
|
||||
fn convert_register_token(chain_id: u64, token: H160, fee: u128) -> (Xcm<()>, Balance) {
|
||||
let network = Ethereum { chain_id };
|
||||
let xcm_fee: Asset = (Location::parent(), fee).into();
|
||||
let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into();
|
||||
|
||||
let total_amount = fee + CreateAssetDeposit::get();
|
||||
let total: Asset = (Location::parent(), total_amount).into();
|
||||
|
||||
let bridge_location: Location = (Parent, Parent, GlobalConsensus(network)).into();
|
||||
|
||||
let owner = GlobalConsensusEthereumConvertsFor::<[u8; 32]>::from_chain_id(&chain_id);
|
||||
let asset_id = Self::convert_token_address(network, token);
|
||||
let create_call_index: [u8; 2] = CreateAssetCall::get();
|
||||
let inbound_queue_pallet_index = InboundQueuePalletInstance::get();
|
||||
|
||||
let xcm: Xcm<()> = vec![
|
||||
// Teleport required fees.
|
||||
ReceiveTeleportedAsset(total.into()),
|
||||
// Pay for execution.
|
||||
BuyExecution { fees: xcm_fee, weight_limit: Unlimited },
|
||||
// Fund the snowbridge sovereign with the required deposit for creation.
|
||||
DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location },
|
||||
// Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`
|
||||
DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()),
|
||||
// Change origin to the bridge.
|
||||
UniversalOrigin(GlobalConsensus(network)),
|
||||
// Call create_asset on foreign assets pallet.
|
||||
Transact {
|
||||
origin_kind: OriginKind::Xcm,
|
||||
require_weight_at_most: Weight::from_parts(400_000_000, 8_000),
|
||||
call: (
|
||||
create_call_index,
|
||||
asset_id,
|
||||
MultiAddress::<[u8; 32], ()>::Id(owner),
|
||||
MINIMUM_DEPOSIT,
|
||||
)
|
||||
.encode()
|
||||
.into(),
|
||||
},
|
||||
RefundSurplus,
|
||||
// Clear the origin so that remaining assets in holding
|
||||
// are claimable by the physical origin (BridgeHub)
|
||||
ClearOrigin,
|
||||
]
|
||||
.into();
|
||||
|
||||
(xcm, total_amount.into())
|
||||
}
|
||||
|
||||
fn convert_send_token(
|
||||
chain_id: u64,
|
||||
token: H160,
|
||||
destination: Destination,
|
||||
amount: u128,
|
||||
asset_hub_fee: u128,
|
||||
) -> (Xcm<()>, Balance) {
|
||||
let network = Ethereum { chain_id };
|
||||
let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into();
|
||||
let asset: Asset = (Self::convert_token_address(network, token), amount).into();
|
||||
|
||||
let (dest_para_id, beneficiary, dest_para_fee) = match destination {
|
||||
// Final destination is a 32-byte account on AssetHub
|
||||
Destination::AccountId32 { id } =>
|
||||
(None, Location::new(0, [AccountId32 { network: None, id }]), 0),
|
||||
// Final destination is a 32-byte account on a sibling of AssetHub
|
||||
Destination::ForeignAccountId32 { para_id, id, fee } => (
|
||||
Some(para_id),
|
||||
Location::new(0, [AccountId32 { network: None, id }]),
|
||||
// Total fee needs to cover execution on AssetHub and Sibling
|
||||
fee,
|
||||
),
|
||||
// Final destination is a 20-byte account on a sibling of AssetHub
|
||||
Destination::ForeignAccountId20 { para_id, id, fee } => (
|
||||
Some(para_id),
|
||||
Location::new(0, [AccountKey20 { network: None, key: id }]),
|
||||
// Total fee needs to cover execution on AssetHub and Sibling
|
||||
fee,
|
||||
),
|
||||
};
|
||||
|
||||
let total_fees = asset_hub_fee.saturating_add(dest_para_fee);
|
||||
let total_fee_asset: Asset = (Location::parent(), total_fees).into();
|
||||
let inbound_queue_pallet_index = InboundQueuePalletInstance::get();
|
||||
|
||||
let mut instructions = vec![
|
||||
ReceiveTeleportedAsset(total_fee_asset.into()),
|
||||
BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited },
|
||||
DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()),
|
||||
UniversalOrigin(GlobalConsensus(network)),
|
||||
ReserveAssetDeposited(asset.clone().into()),
|
||||
ClearOrigin,
|
||||
];
|
||||
|
||||
match dest_para_id {
|
||||
Some(dest_para_id) => {
|
||||
let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into();
|
||||
|
||||
instructions.extend(vec![
|
||||
// Perform a deposit reserve to send to destination chain.
|
||||
DepositReserveAsset {
|
||||
assets: Definite(vec![dest_para_fee_asset.clone(), asset.clone()].into()),
|
||||
dest: Location::new(1, [Parachain(dest_para_id)]),
|
||||
xcm: vec![
|
||||
// Buy execution on target.
|
||||
BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited },
|
||||
// Deposit asset to beneficiary.
|
||||
DepositAsset { assets: Definite(asset.into()), beneficiary },
|
||||
]
|
||||
.into(),
|
||||
},
|
||||
]);
|
||||
},
|
||||
None => {
|
||||
instructions.extend(vec![
|
||||
// Deposit asset to beneficiary.
|
||||
DepositAsset { assets: Definite(asset.into()), beneficiary },
|
||||
]);
|
||||
},
|
||||
}
|
||||
|
||||
(instructions.into(), total_fees.into())
|
||||
}
|
||||
|
||||
// Convert ERC20 token address to a location that can be understood by Assets Hub.
|
||||
fn convert_token_address(network: NetworkId, token: H160) -> Location {
|
||||
Location::new(
|
||||
2,
|
||||
[GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalConsensusEthereumConvertsFor<AccountId>(PhantomData<AccountId>);
|
||||
impl<AccountId> ConvertLocation<AccountId> for GlobalConsensusEthereumConvertsFor<AccountId>
|
||||
where
|
||||
AccountId: From<[u8; 32]> + Clone,
|
||||
{
|
||||
fn convert_location(location: &Location) -> Option<AccountId> {
|
||||
match location.unpack() {
|
||||
(_, [GlobalConsensus(Ethereum { chain_id })]) =>
|
||||
Some(Self::from_chain_id(chain_id).into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId> GlobalConsensusEthereumConvertsFor<AccountId> {
|
||||
pub fn from_chain_id(chain_id: &u64) -> [u8; 32] {
|
||||
(b"ethereum-chain", chain_id).using_encoded(blake2_256)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use super::GlobalConsensusEthereumConvertsFor;
|
||||
use crate::inbound::CallIndex;
|
||||
use frame_support::parameter_types;
|
||||
use hex_literal::hex;
|
||||
use xcm::prelude::*;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
const NETWORK: NetworkId = Ethereum { chain_id: 11155111 };
|
||||
|
||||
parameter_types! {
|
||||
pub EthereumNetwork: NetworkId = NETWORK;
|
||||
|
||||
pub const CreateAssetCall: CallIndex = [1, 1];
|
||||
pub const CreateAssetExecutionFee: u128 = 123;
|
||||
pub const CreateAssetDeposit: u128 = 891;
|
||||
pub const SendTokenExecutionFee: u128 = 592;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contract_location_with_network_converts_successfully() {
|
||||
let expected_account: [u8; 32] =
|
||||
hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d");
|
||||
let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]);
|
||||
|
||||
let account =
|
||||
GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(account, expected_account);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contract_location_with_incorrect_location_fails_convert() {
|
||||
let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]);
|
||||
|
||||
assert_eq!(
|
||||
GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location),
|
||||
None,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user