From 50724a2680e1c6f547f41ebb4bc7b46f8e8671c6 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Thu, 17 Feb 2022 18:47:13 +0800 Subject: [PATCH 01/13] set metadata when register asset --- pallets/xtransfer/src/assets_wrapper.rs | 60 ++++++++++++++++++- .../xtransfer/src/bridge_transfer/tests.rs | 54 ++++++++++++++++- pallets/xtransfer/src/xcm/xcm_transfer.rs | 52 ++++++++++++++++ runtime/khala/src/lib.rs | 2 + runtime/thala/src/lib.rs | 5 +- 5 files changed, 169 insertions(+), 4 deletions(-) diff --git a/pallets/xtransfer/src/assets_wrapper.rs b/pallets/xtransfer/src/assets_wrapper.rs index 3c00b9d7..de7fa7f6 100644 --- a/pallets/xtransfer/src/assets_wrapper.rs +++ b/pallets/xtransfer/src/assets_wrapper.rs @@ -4,7 +4,9 @@ pub use self::pallet::*; #[frame_support::pallet] pub mod pallet { use codec::{Decode, Encode}; - use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::StorageVersion}; + use frame_support::{ + dispatch::DispatchResult, pallet_prelude::*, traits::StorageVersion, transactional, + }; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; use sp_runtime::traits::StaticLookup; @@ -35,11 +37,19 @@ pub mod pallet { // Potential other bridge solutions } + #[derive(Clone, Decode, Encode, Eq, PartialEq, Ord, PartialOrd, Debug, TypeInfo)] + pub struct AssetProperties { + pub name: Vec, + pub symbol: Vec, + pub decimals: u8, + } + #[derive(Clone, Decode, Encode, Eq, PartialEq, Ord, PartialOrd, Debug, TypeInfo)] pub struct AssetRegistryInfo { location: MultiLocation, reserve_location: Option, enabled_bridges: Vec, + properties: AssetProperties, } impl From for XTransferAsset { @@ -215,10 +225,12 @@ pub mod pallet { T: pallet_assets::Config, { #[pallet::weight(195_000_000)] + #[transactional] pub fn force_register_asset( origin: OriginFor, asset: XTransferAsset, asset_id: T::AssetId, + properties: AssetProperties, owner: ::Source, ) -> DispatchResult { T::AssetsCommitteeOrigin::ensure_origin(origin.clone())?; @@ -233,7 +245,7 @@ pub mod pallet { Error::::AssetAlreadyExist ); pallet_assets::pallet::Pallet::::force_create( - origin, + origin.clone(), asset_id, owner, true, @@ -241,6 +253,7 @@ pub mod pallet { )?; IdByAssets::::insert(&asset, asset_id); AssetByIds::::insert(asset_id, &asset); + RegistryInfoByIds::::insert( asset_id, AssetRegistryInfo { @@ -251,8 +264,18 @@ pub mod pallet { config: XBridgeConfig::Xcmp, metadata: Box::new(Vec::new()), }], + properties: properties.clone(), }, ); + pallet_assets::pallet::Pallet::::force_set_metadata( + origin, + asset_id, + properties.name, + properties.symbol, + properties.decimals, + false, + )?; + Self::deposit_event(Event::AssetRegistered { asset_id, asset }); Ok(()) } @@ -262,6 +285,7 @@ pub mod pallet { /// will not success anymore, we should call pallet_assets::destory() manually /// if we want to delete this asset from our chain #[pallet::weight(195_000_000)] + #[transactional] pub fn force_unregister_asset( origin: OriginFor, asset_id: T::AssetId, @@ -293,6 +317,32 @@ pub mod pallet { } #[pallet::weight(195_000_000)] + #[transactional] + pub fn force_set_metadata( + origin: OriginFor, + asset_id: T::AssetId, + properties: AssetProperties, + ) -> DispatchResult { + T::AssetsCommitteeOrigin::ensure_origin(origin.clone())?; + + let mut info = + RegistryInfoByIds::::get(&asset_id).ok_or(Error::::AssetNotRegistered)?; + info.properties = properties.clone(); + RegistryInfoByIds::::insert(&asset_id, &info); + pallet_assets::pallet::Pallet::::force_set_metadata( + origin, + asset_id, + properties.name, + properties.symbol, + properties.decimals, + false, + )?; + + Ok(()) + } + + #[pallet::weight(195_000_000)] + #[transactional] pub fn force_enable_chainbridge( origin: OriginFor, asset_id: T::AssetId, @@ -340,6 +390,7 @@ pub mod pallet { } #[pallet::weight(195_000_000)] + #[transactional] pub fn force_disable_chainbridge( origin: OriginFor, asset_id: T::AssetId, @@ -385,6 +436,7 @@ pub mod pallet { fn id(asset: &XTransferAsset) -> Option; fn asset(id: &AssetId) -> Option; fn lookup_by_resource_id(resource_id: &[u8; 32]) -> Option; + fn decimals(id: &AssetId) -> Option; } impl GetAssetRegistryInfo<::AssetId> for Pallet { @@ -399,5 +451,9 @@ pub mod pallet { fn lookup_by_resource_id(resource_id: &[u8; 32]) -> Option { AssetByResourceIds::::get(resource_id) } + + fn decimals(id: &::AssetId) -> Option { + RegistryInfoByIds::::get(&id).map_or(None, |m| Some(m.properties.decimals)) + } } } diff --git a/pallets/xtransfer/src/bridge_transfer/tests.rs b/pallets/xtransfer/src/bridge_transfer/tests.rs index e8d21aa9..5cc6803f 100644 --- a/pallets/xtransfer/src/bridge_transfer/tests.rs +++ b/pallets/xtransfer/src/bridge_transfer/tests.rs @@ -1,6 +1,8 @@ #![cfg(test)] -use crate::assets_wrapper::pallet::{AccountId32Conversion, GetAssetRegistryInfo, CB_ASSET_KEY}; +use crate::assets_wrapper::pallet::{ + AccountId32Conversion, AssetProperties, GetAssetRegistryInfo, CB_ASSET_KEY, +}; use crate::bridge; use crate::bridge_transfer::mock::{ assert_events, balances, expect_event, new_test_ext, Assets, AssetsWrapper, Balance, Balances, @@ -43,6 +45,11 @@ fn register_asset() { Origin::signed(ALICE), bridge_asset_location.clone().into(), 0, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 12, + }, ALICE, ), DispatchError::BadOrigin @@ -52,6 +59,11 @@ fn register_asset() { Origin::root(), bridge_asset_location.clone().into(), 0, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 12, + }, ALICE, )); @@ -61,6 +73,11 @@ fn register_asset() { Origin::root(), bridge_asset_location.clone().into(), 1, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 12, + }, ALICE, ), crate::pallet_assets_wrapper::Error::::AssetAlreadyExist @@ -82,6 +99,11 @@ fn register_asset() { Origin::root(), another_bridge_asset_location.clone().into(), 0, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 12, + }, ALICE, ), crate::pallet_assets_wrapper::Error::::AssetAlreadyExist @@ -92,6 +114,11 @@ fn register_asset() { Origin::root(), another_bridge_asset_location.clone().into(), 1, + AssetProperties { + name: b"AnotherBridgeAsset".to_vec(), + symbol: b"ABA".to_vec(), + decimals: 12, + }, ALICE, )); assert_eq!( @@ -169,6 +196,11 @@ fn transfer_assets_insufficient_balance() { Origin::root(), bridge_asset_location.clone().into(), 0, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 12, + }, ALICE, )); @@ -219,6 +251,11 @@ fn transfer_assets_to_nonreserve() { Origin::root(), bridge_asset_location.clone().into(), 0, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 12, + }, ALICE, )); @@ -279,6 +316,11 @@ fn transfer_assets_to_reserve() { Origin::root(), bridge_asset_location.clone().into(), 0, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 12, + }, ALICE, )); @@ -441,6 +483,11 @@ fn simulate_transfer_solochainassets_from_reserve_to_local() { Origin::root(), bridge_asset, 0, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 12, + }, ALICE, )); @@ -514,6 +561,11 @@ fn simulate_transfer_solochainassets_from_nonreserve_to_local() { Origin::root(), para_asset.clone(), 0, + AssetProperties { + name: b"ParaAsset".to_vec(), + symbol: b"PA".to_vec(), + decimals: 12, + }, ALICE, )); diff --git a/pallets/xtransfer/src/xcm/xcm_transfer.rs b/pallets/xtransfer/src/xcm/xcm_transfer.rs index d816ac47..1325d8f4 100644 --- a/pallets/xtransfer/src/xcm/xcm_transfer.rs +++ b/pallets/xtransfer/src/xcm/xcm_transfer.rs @@ -9,6 +9,7 @@ pub mod pallet { dispatch::DispatchResult, pallet_prelude::*, traits::{Currency, StorageVersion}, + transactional, weights::Weight, PalletId, }; @@ -115,6 +116,7 @@ pub mod pallet { BalanceOf: Into, { #[pallet::weight(195_000_000 + Pallet::::estimate_transfer_weight())] + #[transactional] pub fn transfer_asset( origin: OriginFor, asset: Box, @@ -132,6 +134,7 @@ pub mod pallet { } #[pallet::weight(195_000_000 + Pallet::::estimate_transfer_weight())] + #[transactional] pub fn transfer_native( origin: OriginFor, dest: MultiLocation, @@ -509,6 +512,11 @@ mod test { Some(ALICE).into(), para_a_asset.clone().into(), 0, + pallet_assets_wrapper::AssetProperties { + name: b"ParaAAsset".to_vec(), + symbol: b"PAA".to_vec(), + decimals: 12, + }, ALICE, ), DispatchError::BadOrigin @@ -518,6 +526,11 @@ mod test { para::Origin::root(), para_a_asset.clone().into(), 0, + pallet_assets_wrapper::AssetProperties { + name: b"ParaAAsset".to_vec(), + symbol: b"PAA".to_vec(), + decimals: 12, + }, ALICE, )); @@ -535,6 +548,20 @@ mod test { para_a_asset ); assert_eq!(ParaAssets::total_supply(0u32.into()), 0); + assert_eq!(ParaAssetsWrapper::id(¶_a_asset).unwrap(), 0u32); + assert_eq!(ParaAssetsWrapper::decimals(&0u32.into()).unwrap(), 12u8); + + // Force set metadata + assert_ok!(ParaAssetsWrapper::force_set_metadata( + para::Origin::root(), + 0, + pallet_assets_wrapper::AssetProperties { + name: b"ParaAAAAsset".to_vec(), + symbol: b"PAAAA".to_vec(), + decimals: 18, + }, + )); + assert_eq!(ParaAssetsWrapper::decimals(&0u32.into()).unwrap(), 18u8); // Same asset location register again, should be failed assert_noop!( @@ -542,6 +569,11 @@ mod test { para::Origin::root(), para_a_asset.clone().into(), 1, + pallet_assets_wrapper::AssetProperties { + name: b"ParaAAsset".to_vec(), + symbol: b"PAA".to_vec(), + decimals: 12, + }, ALICE, ), pallet_assets_wrapper::Error::::AssetAlreadyExist @@ -560,6 +592,11 @@ mod test { para::Origin::root(), para_b_asset.clone().into(), 0, + pallet_assets_wrapper::AssetProperties { + name: b"ParaBAsset".to_vec(), + symbol: b"PBA".to_vec(), + decimals: 12, + }, ALICE, ), pallet_assets_wrapper::Error::::AssetAlreadyExist @@ -576,6 +613,11 @@ mod test { para::Origin::root(), para_b_asset.clone().into(), 1, + pallet_assets_wrapper::AssetProperties { + name: b"ParaBAsset".to_vec(), + symbol: b"PBA".to_vec(), + decimals: 12, + }, ALICE, )); assert_eq!(ParaAssetsWrapper::id(¶_b_asset).unwrap(), 1u32); @@ -611,6 +653,11 @@ mod test { para::Origin::root(), para_a_asset.clone().into(), 0, + pallet_assets_wrapper::AssetProperties { + name: b"ParaAAsset".to_vec(), + symbol: b"PAA".to_vec(), + decimals: 12, + }, ALICE, )); }); @@ -659,6 +706,11 @@ mod test { para::Origin::root(), para_a_asset.clone().into(), 0, + pallet_assets_wrapper::AssetProperties { + name: b"ParaAAsset".to_vec(), + symbol: b"PAA".to_vec(), + decimals: 12, + }, ALICE, )); }); diff --git a/runtime/khala/src/lib.rs b/runtime/khala/src/lib.rs index d9ee2fe7..a9958262 100644 --- a/runtime/khala/src/lib.rs +++ b/runtime/khala/src/lib.rs @@ -297,6 +297,8 @@ impl Contains for BaseCallFilter { return match assets_method { pallet_assets::Call::create { .. } | pallet_assets::Call::force_create { .. } + | pallet_assets::Call::set_metadata { .. } + | pallet_assets::Call::force_set_metadata { .. } | pallet_assets::Call::__Ignore { .. } => { false } diff --git a/runtime/thala/src/lib.rs b/runtime/thala/src/lib.rs index 5bb44fae..19cf5832 100644 --- a/runtime/thala/src/lib.rs +++ b/runtime/thala/src/lib.rs @@ -300,7 +300,10 @@ impl Contains for BaseCallFilter { if let Call::Assets(assets_method) = call { match assets_method { - pallet_assets::Call::create { .. } | pallet_assets::Call::force_create { .. } => { + pallet_assets::Call::create { .. } + | pallet_assets::Call::force_create { .. } + | pallet_assets::Call::set_metadata { .. } + | pallet_assets::Call::force_set_metadata { .. } => { return false; } pallet_assets::Call::__Ignore { .. } => { From 6d1679888e996dd2df74ea1e5fdbe4ed947c23f8 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Thu, 17 Feb 2022 18:47:42 +0800 Subject: [PATCH 02/13] fix bridge fee calculation --- pallets/xtransfer/src/bridge_transfer/mod.rs | 28 +++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pallets/xtransfer/src/bridge_transfer/mod.rs b/pallets/xtransfer/src/bridge_transfer/mod.rs index a2c861c4..198e8e94 100644 --- a/pallets/xtransfer/src/bridge_transfer/mod.rs +++ b/pallets/xtransfer/src/bridge_transfer/mod.rs @@ -516,7 +516,7 @@ pub mod pallet { { fn get_fee(chain_id: bridge::BridgeChainId, asset: &MultiAsset) -> Option { match (&asset.id, &asset.fun) { - (Concrete(asset_id), Fungible(amount)) => { + (Concrete(location), Fungible(amount)) => { let fee_estimated_in_pha = Self::estimate_fee_in_pha(chain_id, (*amount).into()); if T::NativeChecker::is_native_asset(asset) { @@ -525,12 +525,28 @@ pub mod pallet { let fee_in_asset; let fee_prices = T::ExecutionPriceInfo::get(); if let Some(idx) = fee_prices.iter().position(|(fee_asset_id, _)| { - fee_asset_id == &Concrete(asset_id.clone()) + fee_asset_id == &Concrete(location.clone()) }) { - fee_in_asset = Some( - fee_estimated_in_pha.into() * fee_prices[idx].1 - / T::NativeExecutionPrice::get(), - ) + let id = T::AssetsWrapper::id(&XTransferAsset(location.clone())); + if id.is_none() { + fee_in_asset = None; + } else { + let decimals = + T::AssetsWrapper::decimals(&id.unwrap()).unwrap_or(12); + let fee_in_decimal_12 = fee_estimated_in_pha.into() + * fee_prices[idx].1 + / T::NativeExecutionPrice::get(); + + fee_in_asset = if decimals > 12 { + Some(fee_in_decimal_12.saturating_mul( + 10u128.saturating_pow(decimals as u32 - 12), + )) + } else { + Some(fee_in_decimal_12.saturating_div( + 10u128.saturating_pow(12 - decimals as u32), + )) + } + } } else { fee_in_asset = None } From dd9dd2e21514fed1537b913a5da2c42f02ca7462 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Tue, 15 Feb 2022 13:29:53 +0800 Subject: [PATCH 03/13] script of xtransfer fee --- scripts/js/xtransfer_fee.js | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 scripts/js/xtransfer_fee.js diff --git a/scripts/js/xtransfer_fee.js b/scripts/js/xtransfer_fee.js new file mode 100644 index 00000000..1c434f08 --- /dev/null +++ b/scripts/js/xtransfer_fee.js @@ -0,0 +1,100 @@ +const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api'); +const BN = require('bn.js'); + +const bn1e12 = new BN(10).pow(new BN(12)); +const bn1e8 = new BN(10).pow(new BN(8)); +const PHA_PER_SECOND = bn1e12.mul(new BN(80)); +const ASSETS = ['PHA', 'KSM', 'KAR', 'BNC', 'VSKSM', 'ZLK']; +const FEEE_PRICES = [ + PHA_PER_SECOND, + PHA_PER_SECOND.div(new BN(600)), + PHA_PER_SECOND.div(new BN(8)), + PHA_PER_SECOND.div(new BN(4)), + PHA_PER_SECOND.div(new BN(600)), + PHA_PER_SECOND.div(new BN(4)) +]; +const SENDING_XCM_FEE_IN_PHA = bn1e8.mul(new BN(512)); +const RECEIVING_XCM_FEE_IN_PHA = bn1e8.mul(new BN(640)); + +async function getBridgeSendToSoloChainFeeInPha(khalaApi, destChain, amount) { + let result = await khalaApi.query.bridgeTransfer.bridgeFee(destChain); + let minFee = new BN(result[0]); + let feeScale = new BN(result[1]); + let feeExtimated = amount.mul(feeScale).div(new BN(1000)); + return feeExtimated.gte(minFee) ? feeExtimated : minFee; +} + +async function getBridgeSendToSoloChainFee(khalaApi, destChain, asset, amount) { + let feeExtimatedInPha = await getBridgeSendToSoloChainFeeInPha(khalaApi, destChain, amount); + if (asset === 'PHA') { + return feeExtimatedInPha; + } else { + let index = ASSETS.findIndex(item => item === asset); + if (index === -1) throw new Error('Unrecognized asset'); + return feeExtimatedInPha.mul(FEEE_PRICES[index]).div(PHA_PER_SECOND); + } +} + +// Return fee deducted when sending assets to khala from parachians +// Note: fee only affected by asset type +function getXcmSendFromParachainsFee(asset) { + if (asset === 'PHA') { + return RECEIVING_XCM_FEE_IN_PHA; + } else { + let index = ASSETS.findIndex(item => item === asset); + if (index === -1) throw new Error('Unrecognized asset'); + return RECEIVING_XCM_FEE_IN_PHA.mul(FEEE_PRICES[index]).div(PHA_PER_SECOND) + } +} + +// Return fee deducted when sending assets from khala to parachains +// Note: fee only affected by asset type +function getXcmSendFromKhalaFee(asset) { + if (asset === 'PHA') { + return SENDING_XCM_FEE_IN_PHA; + } else { + let index = ASSETS.findIndex(item => item === asset); + if (index === -1) throw new Error('Unrecognized asset'); + return SENDING_XCM_FEE_IN_PHA.mul(FEEE_PRICES[index]).div(PHA_PER_SECOND) + } +} + +async function main() { + const khalaProvider = new WsProvider('ws://localhost:9944'); + const khalaApi = await ApiPromise.create({ + provider: khalaProvider, + }); + + // We use karura as an example of parachain, other parachains like bifrost have + // the same logic + + let bridgeFeeOfPHA = await getBridgeSendToSoloChainFee(khalaApi, 0, 'PHA', bn1e12.mul(new BN(100))); + console.log(`(X2)Fee of transfer PHA from khala to ethereum: ${bridgeFeeOfPHA}`); + let bridgeFeeOfKAR = await getBridgeSendToSoloChainFee(khalaApi, 0, 'KAR', bn1e12.mul(new BN(100))) + console.log(`(X2)Fee of transfer KAR from khala to ethereum: ${bridgeFeeOfKAR}`); + + console.log(`(X2)Fee of transfer PHA from ethereum to khala: ${0}`); + console.log(`(X2)Fee of transfer KAR from ethereum to khala: ${0}`); + + let xcmSendFeeOfPHA = getXcmSendFromKhalaFee('PHA'); + console.log(`(X2)Fee of transfer PHA from khala to karura: ${xcmSendFeeOfPHA}`); + let xcmSendFeeOfKAR = getXcmSendFromKhalaFee('KAR'); + console.log(`(X2)Fee of transfer KAR from khala to karura: ${xcmSendFeeOfKAR}`); + + let xcmReceiveFeeOfPHA = getXcmSendFromParachainsFee('PHA'); + console.log(`(X2)Fee of transfer PHA from karura to khala: ${xcmReceiveFeeOfPHA}`); + let xcmReceiveFeeOfKAR = getXcmSendFromParachainsFee('KAR'); + console.log(`(X2)Fee of transfer KAR from karura to khala: ${xcmReceiveFeeOfKAR}`); + + // X3 send from solochain to parachains has the same fee with transfer from khala + // to other parachains + console.log(`(X3)Fee of transfer PHA from ethereum to karura: ${xcmSendFeeOfPHA}`); + console.log(`(X3)Fee of transfer KAR from ethereum to karura: ${xcmSendFeeOfKAR}`); + + // The most expensive fee should be X3 transfer that send from parachains to solo chains, + // total fee consists of xcm transfer fee and bridge transfer fee + console.log(`(X3)Fee of transfer PHA from karura to ethereum: ${xcmReceiveFeeOfPHA.add(bridgeFeeOfPHA)}`); + console.log(`(X3)Fee of transfer KAR from karura to ethereum: ${xcmReceiveFeeOfKAR.add(bridgeFeeOfKAR)}`); +} + +main().catch(console.error).finally(() => process.exit()); From bd89009d7e996c73ee83433bd16cad69292ccfbf Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Thu, 17 Feb 2022 21:21:31 +0800 Subject: [PATCH 04/13] convert transfer amount to decimals 12 corresponding value before use it calculate fee --- pallets/xtransfer/src/bridge_transfer/mod.rs | 42 +++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/pallets/xtransfer/src/bridge_transfer/mod.rs b/pallets/xtransfer/src/bridge_transfer/mod.rs index 198e8e94..45ea54db 100644 --- a/pallets/xtransfer/src/bridge_transfer/mod.rs +++ b/pallets/xtransfer/src/bridge_transfer/mod.rs @@ -517,22 +517,26 @@ pub mod pallet { fn get_fee(chain_id: bridge::BridgeChainId, asset: &MultiAsset) -> Option { match (&asset.id, &asset.fun) { (Concrete(location), Fungible(amount)) => { - let fee_estimated_in_pha = - Self::estimate_fee_in_pha(chain_id, (*amount).into()); - if T::NativeChecker::is_native_asset(asset) { - Some(fee_estimated_in_pha.into()) + let id = T::AssetsWrapper::id(&XTransferAsset(location.clone())); + if id.is_none() { + None } else { - let fee_in_asset; - let fee_prices = T::ExecutionPriceInfo::get(); - if let Some(idx) = fee_prices.iter().position(|(fee_asset_id, _)| { - fee_asset_id == &Concrete(location.clone()) - }) { - let id = T::AssetsWrapper::id(&XTransferAsset(location.clone())); - if id.is_none() { - fee_in_asset = None; - } else { - let decimals = - T::AssetsWrapper::decimals(&id.unwrap()).unwrap_or(12); + let decimals = T::AssetsWrapper::decimals(&id.unwrap()).unwrap_or(12); + let amount_in_decimal_12 = if decimals > 12 { + amount.saturating_div(10u128.saturating_pow(decimals as u32 - 12)) + } else { + amount.saturating_mul(10u128.saturating_pow(decimals as u32 - 12)) + }; + let fee_estimated_in_pha = + Self::estimate_fee_in_pha(chain_id, (amount_in_decimal_12).into()); + if T::NativeChecker::is_native_asset(asset) { + Some(fee_estimated_in_pha.into()) + } else { + let fee_in_asset; + let fee_prices = T::ExecutionPriceInfo::get(); + if let Some(idx) = fee_prices.iter().position(|(fee_asset_id, _)| { + fee_asset_id == &Concrete(location.clone()) + }) { let fee_in_decimal_12 = fee_estimated_in_pha.into() * fee_prices[idx].1 / T::NativeExecutionPrice::get(); @@ -545,12 +549,12 @@ pub mod pallet { Some(fee_in_decimal_12.saturating_div( 10u128.saturating_pow(12 - decimals as u32), )) - } + }; + } else { + fee_in_asset = None } - } else { - fee_in_asset = None + fee_in_asset } - fee_in_asset } } _ => None, From 73dc5bf1281d09e5099010f2c944d63695cf91b6 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Thu, 17 Feb 2022 21:26:11 +0800 Subject: [PATCH 05/13] update fee estimate script --- scripts/js/xtransfer_fee.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/js/xtransfer_fee.js b/scripts/js/xtransfer_fee.js index 1c434f08..dd05cf1f 100644 --- a/scripts/js/xtransfer_fee.js +++ b/scripts/js/xtransfer_fee.js @@ -1,10 +1,12 @@ const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api'); const BN = require('bn.js'); +const bn1e18 = new BN(10).pow(new BN(18)); const bn1e12 = new BN(10).pow(new BN(12)); const bn1e8 = new BN(10).pow(new BN(8)); const PHA_PER_SECOND = bn1e12.mul(new BN(80)); const ASSETS = ['PHA', 'KSM', 'KAR', 'BNC', 'VSKSM', 'ZLK']; +const ASSETS_IDs = [-1, 0, 1, 2, 3, 4]; const FEEE_PRICES = [ PHA_PER_SECOND, PHA_PER_SECOND.div(new BN(600)), @@ -31,7 +33,14 @@ async function getBridgeSendToSoloChainFee(khalaApi, destChain, asset, amount) { } else { let index = ASSETS.findIndex(item => item === asset); if (index === -1) throw new Error('Unrecognized asset'); - return feeExtimatedInPha.mul(FEEE_PRICES[index]).div(PHA_PER_SECOND); + + let fee_in_decimal_12 = feeExtimatedInPha.mul(FEEE_PRICES[index]).div(PHA_PER_SECOND); + let decimals = (await khalaApi.query.assetsWrapper.registryInfoByIds(ASSETS_IDs[index])).toJSON().properties.decimals; + if (decimals > 12) { + return fee_in_decimal_12.mul(new BN(10).pow(new BN(decimals - 12))); + } else { + return fee_in_decimal_12.div(new BN(10).pow(new BN(12 - decimals))); + } } } @@ -72,6 +81,11 @@ async function main() { console.log(`(X2)Fee of transfer PHA from khala to ethereum: ${bridgeFeeOfPHA}`); let bridgeFeeOfKAR = await getBridgeSendToSoloChainFee(khalaApi, 0, 'KAR', bn1e12.mul(new BN(100))) console.log(`(X2)Fee of transfer KAR from khala to ethereum: ${bridgeFeeOfKAR}`); + // Note: need to convert to decimals_12_amount when calculate asset that decimals is not 12 + // For example, here amount we transfer is bn1e18.mul(new BN(100)), but when calculate fee, + // need to convert to bn1e12.mul(new BN(100)) + let bridgeFeeOfZLK = await getBridgeSendToSoloChainFee(khalaApi, 0, 'ZLK', bn1e12.mul(new BN(100))) + console.log(`(X2)Fee of transfer ZLK from khala to ethereum: ${bridgeFeeOfZLK}`); console.log(`(X2)Fee of transfer PHA from ethereum to khala: ${0}`); console.log(`(X2)Fee of transfer KAR from ethereum to khala: ${0}`); From a94a368194be8eb1f91189c08d80f0866c48a42f Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Fri, 18 Feb 2022 00:01:30 +0800 Subject: [PATCH 06/13] fix & log --- pallets/xtransfer/src/bridge_transfer/mod.rs | 2 +- pallets/xtransfer/src/xcm/xcm_helper.rs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pallets/xtransfer/src/bridge_transfer/mod.rs b/pallets/xtransfer/src/bridge_transfer/mod.rs index 45ea54db..01ae3f87 100644 --- a/pallets/xtransfer/src/bridge_transfer/mod.rs +++ b/pallets/xtransfer/src/bridge_transfer/mod.rs @@ -525,7 +525,7 @@ pub mod pallet { let amount_in_decimal_12 = if decimals > 12 { amount.saturating_div(10u128.saturating_pow(decimals as u32 - 12)) } else { - amount.saturating_mul(10u128.saturating_pow(decimals as u32 - 12)) + amount.saturating_mul(10u128.saturating_pow(12 - decimals as u32)) }; let fee_estimated_in_pha = Self::estimate_fee_in_pha(chain_id, (amount_in_decimal_12).into()); diff --git a/pallets/xtransfer/src/xcm/xcm_helper.rs b/pallets/xtransfer/src/xcm/xcm_helper.rs index 812204e2..4458c36f 100644 --- a/pallets/xtransfer/src/xcm/xcm_helper.rs +++ b/pallets/xtransfer/src/xcm/xcm_helper.rs @@ -242,10 +242,21 @@ pub mod xcm_helper { .clone() .try_into() .map_err(|_| XcmError::FailedToTransactAsset("ChainIdConversionFailed"))?; - + log::trace!( + target: LOG_TARGET, + "Forward transaction to chain: {:?}, with asset: {:?}", + dest_id, + &what, + ); // Deduct some fees if assets would be forwarded to solo chains. let fee = BridgeFeeInfo::get_fee(dest_id, what) .ok_or(XcmError::FailedToTransactAsset("FailedGetFee"))?; + log::trace!( + target: LOG_TARGET, + "Deduct some fees before transfer to solochain, fee: {:?}, amount: {:?}", + fee, + amount + ); ensure!( amount > fee, XcmError::FailedToTransactAsset("Insufficient") @@ -259,6 +270,12 @@ pub mod xcm_helper { id: Treasury::get().into(), }), ); + log::trace!( + target: LOG_TARGET, + "Send fee to treasury, fee: {:?}, treasury: {:?}", + &fee_asset, + &treasury + ); if NativeChecker::is_native_asset(&fee_asset) { NativeAdapter::deposit_asset(&fee_asset, &treasury) .map_err(|_| XcmError::FailedToTransactAsset("FeeTransferFailed"))?; From 92dd16ac4f6577ce099070b7586faa6510b14c54 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Fri, 18 Feb 2022 12:14:09 +0800 Subject: [PATCH 07/13] index script --- scripts/js/package.json | 2 + scripts/js/xtransfer_index.js | 338 ++++++++++++++++++++++++++++++++++ scripts/js/yarn.lock | 33 ++++ 3 files changed, 373 insertions(+) create mode 100644 scripts/js/xtransfer_index.js diff --git a/scripts/js/package.json b/scripts/js/package.json index 41690c5d..d95be094 100644 --- a/scripts/js/package.json +++ b/scripts/js/package.json @@ -13,6 +13,8 @@ "@polkadot/util-crypto": "^8.1.2", "dotenv": "^10.0.0", "ethers": "^5.5.2", + "graphql": "^16.3.0", + "graphql-request": "^4.0.0", "yargs": "^17.2.1" } } diff --git a/scripts/js/xtransfer_index.js b/scripts/js/xtransfer_index.js new file mode 100644 index 00000000..9ccacd27 --- /dev/null +++ b/scripts/js/xtransfer_index.js @@ -0,0 +1,338 @@ +const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api'); +const { encodeAddress, decodeAddress } = require("@polkadot/util-crypto"); +const { MagicNumber } = require('@polkadot/types/metadata/MagicNumber'); +const BN = require('bn.js'); +const { gql, GraphQLClient } = require('graphql-request'); + +const bn1e12 = new BN(10).pow(new BN(12)); + +const khalaEndpoint = 'ws://127.0.0.1:9944' +const karuraEndpoint = 'ws://127.0.0.1:9955' + +const khalaQueryEndpoint = 'https://api.subquery.network/sq/tolak/kahla-subquery-dev__dG9sY'; +const karuraQueryEndpoint = 'https://api.subquery.network/sq/tolak/karura-subquery-dev__dG9sY'; +const rinkebyQueryEndpoint = 'https://api.thegraph.com/subgraphs/name/tolak/khala-rinkeby-chainbridge'; + +const rinkebyGqlClient = new GraphQLClient(rinkebyQueryEndpoint, { timeout: 300000 }); +const khalaGqlClient = new GraphQLClient(khalaQueryEndpoint, { timeout: 300000 }); +const karuraGqlClient = new GraphQLClient(karuraQueryEndpoint, { timeout: 300000 }); + +async function getEvmSendHistory(account) { + let data; + try { + data = await rinkebyGqlClient.request(gql` + { + bridgeOutboundingRecords (orderBy: createdAt, orderDirection: desc, filter: {sender: {equalTo: \"${account}\"}}) { + id + createdAt + destChainId + depositNonce + resourceId + amount + recipient + sendTx { + hash + } + sender + } + } + `); + } catch (e) { + throw new Error( + "Error getting bridgeOutboundingRecords from blockchain: " + + JSON.stringify(e) + + JSON.stringify(data) + ); + } + return data; +} + +async function getEvmReceiveHistory(originChainId, depositNonce) { + let data; + try { + data = await rinkebyGqlClient.request(gql` + { + bridgeInboundingRecords (orderBy: createdAt, orderDirection: desc, filter: {originChainId: {equalTo: \"${originChainId}\"}, depositNonce: {equalTo: \"${depositNonce}\"}}) { + id + createdAt + originChainId + depositNonce + resourceId + status + executeTx { + hash + } + } + } + `); + } catch (e) { + throw new Error( + "Error getting bridgeOutboundingRecords from blockchain: " + + JSON.stringify(e) + + JSON.stringify(data) + ); + } + return data; +} + +async function getKhalaSendHistory(sender) { + let data; + try { + data = await khalaGqlClient.request(gql` + { + bridgeOutboundingRecords (orderBy: CREATED_AT_DESC, filter: {sender: {equalTo: \"${sender}\"}}) { + nodes { + id + createdAt + destChainId + depositNonce + resourceId + amount + recipient + sendTx + sender + } + } + } + `); + } catch (e) { + throw new Error( + "Error getting revenue from blockchain: " + + JSON.stringify(e) + + JSON.stringify(data) + ); + } + + return data; +} + +async function getKhalaSendHistoryByRecipient(recipient) { + let data; + try { + data = await khalaGqlClient.request(gql` + { + bridgeOutboundingRecords (orderBy: CREATED_AT_DESC, filter: {recipient: {equalTo: \"${recipient}\"}}) { + nodes { + id + createdAt + destChainId + depositNonce + resourceId + amount + recipient + sendTx + sender + } + } + } + `); + } catch (e) { + throw new Error( + "Error getting revenue from blockchain: " + + JSON.stringify(e) + + JSON.stringify(data) + ); + } + + return data; +} + +async function getKhalaReceiveHistory(originChainId, depositNonce) { + let data; + try { + data = await khalaGqlClient.request(gql` + { + bridgeInboundingRecords (filter: {originChainId: {equalTo: \"${originChainId}\"}, depositNonce: {equalTo: \"${depositNonce}\"}}) { + nodes { + id + createdAt + originChainId + depositNonce + resourceId + status + executeTx + } + } + } + `); + } catch (e) { + throw new Error( + "Error getting revenue from blockchain: " + + JSON.stringify(e) + + JSON.stringify(data) + ); + } + + return data; +} + +async function getKaruraSendHistory(sender) { + let data; + try { + data = await karuraGqlClient.request(gql` + { + xTokenSents (orderBy: CREATED_AT_DESC, filter: {sender: {equalTo: \"${sender}\"}"}}) { + nodes { + id + createdAt + sender + hash + destChain + recipient + amount + currencyId + isX3 + } + } + } + `); + } catch (e) { + throw new Error( + "Error getting revenue from blockchain: " + + JSON.stringify(e) + + JSON.stringify(data) + ); + } + + return data; +} + +async function getKaruraReceiveHistory(recipient) { + let data; + try { + data = await karuraGqlClient.request(gql` + { + currencyDeposits (filter: {recipient: {equalTo: \"${recipient}\"}"}}) { + nodes { + id + createdAt + currencyId + recipient + amount + } + } + } + `); + } catch (e) { + throw new Error( + "Error getting revenue from blockchain: " + + JSON.stringify(e) + + JSON.stringify(data) + ); + } + + return data; +} + +function evmHistoryFilter(api, evmHistory) { + let x2History = []; + let x3History = []; + evmHistory.bridgeOutboundingRecords.filter(history => { + try { + let dest = api.createType('XcmV1MultiLocation', history.recipient).toJSON(); + if (dest.parents === 0 && dest.interior.hasOwnProperty('x1') && dest.interior.x1.hasOwnProperty('accountId32')) { // to khala + history.recipient = encodeAddress(dest.interior.x1.accountId32.id, 42).toString(); + x2History.push(history); + } else if (dest.parents === 1 && dest.interior.hasOwnProperty('x1') && dest.interior.x1.hasOwnProperty('accountId32')) { // to relaychain + history.recipient = encodeAddress(dest.interior.x1.accountId32.id, 42).toString(); + x3History.push(history); + } else if (dest.parents === 1 && dest.interior.hasOwnProperty('x2') && dest.interior.x2[0].hasOwnProperty('parachain') && dest.interior.x2[1].hasOwnProperty('accountId32')) { // to other parachians + history.recipient = encodeAddress(dest.interior.x2[1].accountId32.id, 42).toString(); + history.destPara = dest.interior.x2[0].parachain; + x3History.push(history); + } else { + throw new Error('EDEST'); + } + } catch (e) { + // Old recipient was not encoded from MultiLocation, it was just an khala account + if (e.message !== 'EDEST') { + x2History.push(history); + } else { + throw e; + } + } + }); + + return { + x2History: x2History, + x3History: x3History + }; +} + +function karuraHistoryFilter(api, karuraSendHistory) { + let x2History = []; + let x3History = []; + karuraSendHistory.nodes.filter(history => { + if (history.isX3) { + x3History.push(history); + } else { + x2History.push(history); + } + }); + + return { + x2History: x2History, + x3History: x3History + }; +} + +async function main() { + const khalaProvider = new WsProvider(khalaEndpoint); + const khalaApi = await ApiPromise.create({ + provider: khalaProvider, + }); + + const karuraProvider = new WsProvider(karuraEndpoint); + const karuraApi = await ApiPromise.create({ + provider: karuraProvider, + }); + + const keyring = new Keyring({ type: 'sr25519' }); + const substrateAccount = keyring.addFromUri('//Alice'); + + const rinkebyAccount = '0xA29D4E0F035cb50C0d78c8CeBb56Ca292616Ab20'; + // let privateKey = process.env.KEY; + // let provider = new ethers.providers.JsonRpcProvider('https://rinkeby.infura.io/v3/6d61e7957c1c489ea8141e947447405b'); + // let ethereumWallet = new ethers.Wallet(privateKey, provider); + // let bridge = new ethers.Contract(bridgeAddressOnRinkeby, BridgeJson.abi, ethereumWallet); + + // Get EVM account send history + let evmSendHistory = await getEvmSendHistory(rinkebyAccount); + // Filter x2 and x3 history + let filterSendResult = evmHistoryFilter(khalaApi, evmSendHistory); + + // x2 query history(Here we just pick the latest one to daemon) + let evmX2QueryHistory = filterSendResult.x2History[0]; + let evmX2Confirmation = await getKhalaReceiveHistory(evmX2QueryHistory.destChainId, evmX2QueryHistory.depositNonce); + console.log(`===> X2(from EVM) confirm results on khala:\n${JSON.stringify(evmX2Confirmation, null, 4)}`); + + // x3 query history(Here we just pick the latest one to daemon) + let evmX3QueryHistory = filterSendResult.x3History[0]; + let evmX3ForwardInfo = await getKhalaReceiveHistory(evmX3QueryHistory.destChainId, evmX3QueryHistory.depositNonce); + console.log(`===> X3(from EVM) forward results on khala:\n${JSON.stringify(evmX3ForwardInfo, null, 4)}`); + let evmX3Confirmation = await getKaruraReceiveHistory(evmX3QueryHistory.recipient); + console.log(`===> X3(from EVM) confirmation on khala:\n${JSON.stringify(evmX3Confirmation, null, 4)}`); + + /*********************** Following logic is transfer that send to from other parachians to EVM ******************** */ + // Get substrate account send history(x3) + let karuraSendHistory = await getKaruraSendHistory(substrateAccount.address); + console.log(`===> Send history from karura:\n${JSON.stringify(karuraSendHistory, null, 4)}`); + + // For x2(e.g. send from karura to khala through xcm), field `isX3` should be false, + // then check khala xcmDeposit records + let karuraX2SendHistory = karuraSendHistory.x2History[0]; + + // For x3(e.g. send from karura to EVM through xcm and bridge), field `isX3` should be true, + // get forward info by BridgeOutboundingRecord on khala and get confirmation result by + // BridgeInboudingRecord in EVM chains + let karuraX3SendHistory = karuraSendHistory.x3History[0]; + // Use `recipient` to associate with BridgeOutboundingRecord in khala + // Note: same `recipient` may received more than one asset, so we don't know which one is acctually + // associate the origin send transaction, so just use latest to daemon the example + let karuraX3ForwardInfo = (await getKhalaSendHistoryByRecipient(karuraX3SendHistory.recipient))[0]; + console.log(`===> X3(from karura) forward results on khala:\n${JSON.stringify(karuraX2ForwardInfo, null, 4)}`); + let karuraX3Confirmation = await getEvmReceiveHistory(karuraX3ForwardInfo.destChainId, karuraX3ForwardInfo.depositNonce); + console.log(`===> X3(from karura) confirmation on EVM:\n${JSON.stringify(karuraX3Confirmation, null, 4)}`); +} + +main().catch(console.error).finally(() => process.exit()); diff --git a/scripts/js/yarn.lock b/scripts/js/yarn.lock index dfe5efc4..cb2ae613 100644 --- a/scripts/js/yarn.lock +++ b/scripts/js/yarn.lock @@ -669,6 +669,13 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +cross-fetch@^3.0.6: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -798,6 +805,11 @@ ext@^1.1.2: dependencies: type "^2.5.0" +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -812,6 +824,20 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +graphql-request@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-4.0.0.tgz#5e4361d33df1a95ccd7ad23a8ebb6bbca9d5622f" + integrity sha512-cdqQLCXlBGkaLdkLYRl4LtkwaZU6TfpE7/tnUQFl3wXfUPWN74Ov+Q61VuIh+AltS789YfGB6whghmCmeXLvTw== + dependencies: + cross-fetch "^3.0.6" + extract-files "^9.0.0" + form-data "^3.0.0" + +graphql@^16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05" + integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A== + hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -891,6 +917,13 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.6: version "2.6.6" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" From d430130a94be19ecb7b64e5c8fd7ba1eeb329806 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Fri, 18 Feb 2022 14:46:27 +0800 Subject: [PATCH 08/13] typo --- scripts/js/xtransfer_index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/js/xtransfer_index.js b/scripts/js/xtransfer_index.js index 9ccacd27..1ed45888 100644 --- a/scripts/js/xtransfer_index.js +++ b/scripts/js/xtransfer_index.js @@ -301,12 +301,12 @@ async function main() { // Filter x2 and x3 history let filterSendResult = evmHistoryFilter(khalaApi, evmSendHistory); - // x2 query history(Here we just pick the latest one to daemon) + // x2 query history(Here we just pick the latest one to demonstrate) let evmX2QueryHistory = filterSendResult.x2History[0]; let evmX2Confirmation = await getKhalaReceiveHistory(evmX2QueryHistory.destChainId, evmX2QueryHistory.depositNonce); console.log(`===> X2(from EVM) confirm results on khala:\n${JSON.stringify(evmX2Confirmation, null, 4)}`); - // x3 query history(Here we just pick the latest one to daemon) + // x3 query history(Here we just pick the latest one to demonstrate) let evmX3QueryHistory = filterSendResult.x3History[0]; let evmX3ForwardInfo = await getKhalaReceiveHistory(evmX3QueryHistory.destChainId, evmX3QueryHistory.depositNonce); console.log(`===> X3(from EVM) forward results on khala:\n${JSON.stringify(evmX3ForwardInfo, null, 4)}`); @@ -328,7 +328,7 @@ async function main() { let karuraX3SendHistory = karuraSendHistory.x3History[0]; // Use `recipient` to associate with BridgeOutboundingRecord in khala // Note: same `recipient` may received more than one asset, so we don't know which one is acctually - // associate the origin send transaction, so just use latest to daemon the example + // associate the origin send transaction, so just use latest to demonstrate the example let karuraX3ForwardInfo = (await getKhalaSendHistoryByRecipient(karuraX3SendHistory.recipient))[0]; console.log(`===> X3(from karura) forward results on khala:\n${JSON.stringify(karuraX2ForwardInfo, null, 4)}`); let karuraX3Confirmation = await getEvmReceiveHistory(karuraX3ForwardInfo.destChainId, karuraX3ForwardInfo.depositNonce); From dd5a6f66546e6fbf8372ca907c0f12560391bd37 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Tue, 22 Feb 2022 17:43:09 +0800 Subject: [PATCH 09/13] fix transfer from EVM to other parachains --- pallets/xtransfer/src/bridge_transfer/mod.rs | 58 +++++++++----------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/pallets/xtransfer/src/bridge_transfer/mod.rs b/pallets/xtransfer/src/bridge_transfer/mod.rs index 01ae3f87..78d726c8 100644 --- a/pallets/xtransfer/src/bridge_transfer/mod.rs +++ b/pallets/xtransfer/src/bridge_transfer/mod.rs @@ -397,44 +397,38 @@ pub mod pallet { } // To relaychain or other parachain, forward it by xcm (1, X1(AccountId32 { .. })) | (1, X2(Parachain(_), AccountId32 { .. })) => { - let dest_reserve_account = dest_reserve_location.clone().into_account(); - if asset_reserve_location != dest_reserve_location { - log::trace!( - target: LOG_TARGET, - "Reserve of asset and dest dismatch, deposit asset to dest reserve location.", + let temporary_account = + MultiLocation::new(0, X1(GeneralKey(b"bridge_transfer".to_vec()))) + .into_account(); + log::trace!( + target: LOG_TARGET, + "Deposit withdrawn asset to a temporary account: {:?}", + &temporary_account, + ); + if rid == Self::gen_pha_rid(src_chainid) { + ::Currency::deposit_creating( + &temporary_account.clone().into(), + amount, ); - if rid == Self::gen_pha_rid(src_chainid) { - ::Currency::deposit_creating( - &dest_reserve_account.clone().into(), - amount, - ); - } else { - let asset_id = Self::rid_to_assetid(&rid)?; - let asset_amount = - T::BalanceConverter::to_asset_balance(amount, asset_id) - .map_err(|_| Error::::BalanceConversionFailed)?; - // Mint asset into dest reserve account - pallet_assets::pallet::Pallet::::mint_into( - asset_id, - &dest_reserve_account.clone().into(), - asset_amount, - ) - .map_err(|_| Error::::FailedToTransactAsset)?; - } + } else { + let asset_id = Self::rid_to_assetid(&rid)?; + let asset_amount = T::BalanceConverter::to_asset_balance(amount, asset_id) + .map_err(|_| Error::::BalanceConversionFailed)?; + // Mint asset into dest temporary account + pallet_assets::pallet::Pallet::::mint_into( + asset_id, + &temporary_account.clone().into(), + asset_amount, + ) + .map_err(|_| Error::::FailedToTransactAsset)?; } - // Two main tasks of transfer_fungible is: - // first) withdraw asset from reserve_id - // second) deposit asset into sovereign account of dest chain.(MINT OP) - // - // So if the reserve account does not have enough asset, transaction would fail. - // When someone transfer assets to EVM account from local chain our other parachains, - // assets would be deposited into reserve account, in other words, bridge transfer - // always based on reserve mode. + // After deposited asset into the temporary account, let xcm executor determine how to + // handle the asset. T::XcmTransactor::transfer_fungible( Junction::AccountId32 { network: NetworkId::Any, - id: dest_reserve_account, + id: temporary_account, } .into(), (asset_location, amount.into()).into(), From 8609575eae02798f415e2268dcc247a686a68b88 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Tue, 22 Feb 2022 19:43:11 +0800 Subject: [PATCH 10/13] update --- pallets/xtransfer/src/assets_wrapper.rs | 2 +- pallets/xtransfer/src/bridge_transfer/mod.rs | 88 +++++++++++--------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/pallets/xtransfer/src/assets_wrapper.rs b/pallets/xtransfer/src/assets_wrapper.rs index de7fa7f6..4fab0298 100644 --- a/pallets/xtransfer/src/assets_wrapper.rs +++ b/pallets/xtransfer/src/assets_wrapper.rs @@ -453,7 +453,7 @@ pub mod pallet { } fn decimals(id: &::AssetId) -> Option { - RegistryInfoByIds::::get(&id).map_or(None, |m| Some(m.properties.decimals)) + RegistryInfoByIds::::get(&id).map(|m| m.properties.decimals) } } } diff --git a/pallets/xtransfer/src/bridge_transfer/mod.rs b/pallets/xtransfer/src/bridge_transfer/mod.rs index 78d726c8..b87ca759 100644 --- a/pallets/xtransfer/src/bridge_transfer/mod.rs +++ b/pallets/xtransfer/src/bridge_transfer/mod.rs @@ -322,9 +322,6 @@ pub mod pallet { let dest_location: MultiLocation = Decode::decode(&mut dest.as_slice()).map_err(|_| Error::::DestUnrecognized)?; - let dest_reserve_location = dest_location - .reserve_location() - .ok_or(Error::::DestUnrecognized)?; let asset_location = Self::rid_to_location(&rid)?; let asset_reserve_location = asset_location @@ -450,7 +447,10 @@ pub mod pallet { } } - impl Pallet { + impl Pallet + where + BalanceOf: From + Into, + { // TODO.wf: A more proper way to estimate fee pub fn estimate_fee_in_pha( dest_id: bridge::BridgeChainId, @@ -465,6 +465,38 @@ pub mod pallet { } } + pub fn convert_fee_from_pha(fee_in_pha: BalanceOf, asset: &MultiAsset) -> Option { + match (&asset.id, &asset.fun) { + (Concrete(location), _) => { + let id = T::AssetsWrapper::id(&XTransferAsset(location.clone()))?; + let decimals = T::AssetsWrapper::decimals(&id).unwrap_or(12); + let fee_in_asset; + let fee_prices = T::ExecutionPriceInfo::get(); + if let Some(idx) = fee_prices + .iter() + .position(|(fee_asset_id, _)| fee_asset_id == &Concrete(location.clone())) + { + let fee_e12 = + fee_in_pha.into() * fee_prices[idx].1 / T::NativeExecutionPrice::get(); + + fee_in_asset = if decimals > 12 { + Some( + fee_e12.saturating_mul(10u128.saturating_pow(decimals as u32 - 12)), + ) + } else { + Some( + fee_e12.saturating_div(10u128.saturating_pow(12 - decimals as u32)), + ) + }; + } else { + fee_in_asset = None + } + fee_in_asset + } + _ => None, + } + } + pub fn rid_to_location(rid: &[u8; 32]) -> Result { let src_chainid: bridge::BridgeChainId = Self::get_chainid(rid); let asset_location: MultiLocation = if *rid == Self::gen_pha_rid(src_chainid) { @@ -511,44 +543,18 @@ pub mod pallet { fn get_fee(chain_id: bridge::BridgeChainId, asset: &MultiAsset) -> Option { match (&asset.id, &asset.fun) { (Concrete(location), Fungible(amount)) => { - let id = T::AssetsWrapper::id(&XTransferAsset(location.clone())); - if id.is_none() { - None + let id = T::AssetsWrapper::id(&XTransferAsset(location.clone()))?; + let decimals = T::AssetsWrapper::decimals(&id).unwrap_or(12); + let amount_e12 = if decimals > 12 { + amount.saturating_div(10u128.saturating_pow(decimals as u32 - 12)) } else { - let decimals = T::AssetsWrapper::decimals(&id.unwrap()).unwrap_or(12); - let amount_in_decimal_12 = if decimals > 12 { - amount.saturating_div(10u128.saturating_pow(decimals as u32 - 12)) - } else { - amount.saturating_mul(10u128.saturating_pow(12 - decimals as u32)) - }; - let fee_estimated_in_pha = - Self::estimate_fee_in_pha(chain_id, (amount_in_decimal_12).into()); - if T::NativeChecker::is_native_asset(asset) { - Some(fee_estimated_in_pha.into()) - } else { - let fee_in_asset; - let fee_prices = T::ExecutionPriceInfo::get(); - if let Some(idx) = fee_prices.iter().position(|(fee_asset_id, _)| { - fee_asset_id == &Concrete(location.clone()) - }) { - let fee_in_decimal_12 = fee_estimated_in_pha.into() - * fee_prices[idx].1 - / T::NativeExecutionPrice::get(); - - fee_in_asset = if decimals > 12 { - Some(fee_in_decimal_12.saturating_mul( - 10u128.saturating_pow(decimals as u32 - 12), - )) - } else { - Some(fee_in_decimal_12.saturating_div( - 10u128.saturating_pow(12 - decimals as u32), - )) - }; - } else { - fee_in_asset = None - } - fee_in_asset - } + amount.saturating_mul(10u128.saturating_pow(12 - decimals as u32)) + }; + let fee_in_pha = Self::estimate_fee_in_pha(chain_id, (amount_e12).into()); + if T::NativeChecker::is_native_asset(asset) { + Some(fee_in_pha.into()) + } else { + Self::convert_fee_from_pha(fee_in_pha, asset) } } _ => None, From e55a175f9b6ec78d91611609e08e0364fe9e89a3 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Tue, 22 Feb 2022 19:43:27 +0800 Subject: [PATCH 11/13] test get_fee --- pallets/xtransfer/src/bridge_transfer/mock.rs | 2 +- .../xtransfer/src/bridge_transfer/tests.rs | 78 ++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/pallets/xtransfer/src/bridge_transfer/mock.rs b/pallets/xtransfer/src/bridge_transfer/mock.rs index 62bed584..2508bc2a 100644 --- a/pallets/xtransfer/src/bridge_transfer/mock.rs +++ b/pallets/xtransfer/src/bridge_transfer/mock.rs @@ -131,7 +131,7 @@ parameter_types! { ); pub ExecutionPriceInAsset2: (AssetId, u128) = ( AssetId2::get(), - 1 + 2 ); pub NativeExecutionPrice: u128 = 1; pub ExecutionPrices: Vec<(AssetId, u128)> = [ diff --git a/pallets/xtransfer/src/bridge_transfer/tests.rs b/pallets/xtransfer/src/bridge_transfer/tests.rs index 5cc6803f..31a1f3ef 100644 --- a/pallets/xtransfer/src/bridge_transfer/tests.rs +++ b/pallets/xtransfer/src/bridge_transfer/tests.rs @@ -10,6 +10,8 @@ use crate::bridge_transfer::mock::{ SoloChain2AssetLocation, Test, ALICE, ENDOWED_BALANCE, RELAYER_A, RELAYER_B, RELAYER_C, TREASURY, }; +use crate::bridge_transfer::GetBridgeFee; + use codec::Encode; use frame_support::{assert_noop, assert_ok}; use sp_runtime::DispatchError; @@ -346,7 +348,8 @@ fn transfer_assets_to_reserve() { )); assert_eq!(Assets::balance(0, &ALICE), amount); - assert_eq!(Assets::balance(0, &TREASURY::get()), 2); + // Rate of PHA and SoloChain2AssetLocation accoate asset is 2:1 + assert_eq!(Assets::balance(0, &TREASURY::get()), 4); // The asset's reserve chain is 2, dest chain is 2, // so assets just be burned from sender @@ -729,3 +732,76 @@ fn create_successful_transfer_proposal() { ]); }) } + +#[test] +fn test_get_fee() { + new_test_ext().execute_with(|| { + let dest_chain: u8 = 2; + let test_asset_location = MultiLocation::new(1, X1(GeneralKey(b"test".to_vec()))); + assert_ok!(BridgeTransfer::update_fee(Origin::root(), 2, 0, dest_chain)); + + // Register asset, decimals: 18, rate with pha: 1 : 1 + assert_ok!(AssetsWrapper::force_register_asset( + Origin::root(), + SoloChain0AssetLocation::get().into(), + 0, + AssetProperties { + name: b"BridgeAsset".to_vec(), + symbol: b"BA".to_vec(), + decimals: 18, + }, + ALICE, + )); + // Register asset, decimals: 12, rate with pha: 1 : 2 + assert_ok!(AssetsWrapper::force_register_asset( + Origin::root(), + SoloChain2AssetLocation::get().into(), + 1, + AssetProperties { + name: b"AnotherBridgeAsset".to_vec(), + symbol: b"ABA".to_vec(), + decimals: 12, + }, + ALICE, + )); + + // Register asset, decimals: 12, not set as fee payment + assert_ok!(AssetsWrapper::force_register_asset( + Origin::root(), + test_asset_location.clone().into(), + 2, + AssetProperties { + name: b"TestAsset".to_vec(), + symbol: b"TA".to_vec(), + decimals: 12, + }, + ALICE, + )); + + let asset0: MultiAsset = ( + SoloChain0AssetLocation::get(), + Fungible(100_000_000_000_000_000_000), + ) + .into(); + let asset2: MultiAsset = ( + SoloChain2AssetLocation::get(), + Fungible(100_000_000_000_000), + ) + .into(); + let test_asset: MultiAsset = (test_asset_location, Fungible(100)).into(); + + // Test asset not configured as fee payment in trader + assert_eq!(BridgeTransfer::get_fee(dest_chain, &test_asset), None); + // Fee in pha is 2, decimal of balance is not set so will be set as 12 when calculating fee, + // asset 0 decimals is 18, and rate with pha is 1:1 + // Final fee in asset 0 is 2_000_000 + assert_eq!( + BridgeTransfer::get_fee(dest_chain, &asset0), + Some(2_000_000), + ); + // Fee in pha is 2, decimal of balance is not set so will be set as 12 when calculating fee, + // asset 2 decimals is 12, and rate with pha is 2:1 + // Final fee in asset 2 is 4 + assert_eq!(BridgeTransfer::get_fee(dest_chain, &asset2), Some(4),); + }) +} From c1f1fcf7d11649568672e27a205b975a30521724 Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Wed, 23 Feb 2022 23:02:56 +0800 Subject: [PATCH 12/13] simplify fee calculation --- pallets/xtransfer/src/bridge_transfer/mod.rs | 72 ++++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/pallets/xtransfer/src/bridge_transfer/mod.rs b/pallets/xtransfer/src/bridge_transfer/mod.rs index b87ca759..d67cf054 100644 --- a/pallets/xtransfer/src/bridge_transfer/mod.rs +++ b/pallets/xtransfer/src/bridge_transfer/mod.rs @@ -465,38 +465,27 @@ pub mod pallet { } } - pub fn convert_fee_from_pha(fee_in_pha: BalanceOf, asset: &MultiAsset) -> Option { - match (&asset.id, &asset.fun) { - (Concrete(location), _) => { - let id = T::AssetsWrapper::id(&XTransferAsset(location.clone()))?; - let decimals = T::AssetsWrapper::decimals(&id).unwrap_or(12); - let fee_in_asset; - let fee_prices = T::ExecutionPriceInfo::get(); - if let Some(idx) = fee_prices - .iter() - .position(|(fee_asset_id, _)| fee_asset_id == &Concrete(location.clone())) - { - let fee_e12 = - fee_in_pha.into() * fee_prices[idx].1 / T::NativeExecutionPrice::get(); - - fee_in_asset = if decimals > 12 { - Some( - fee_e12.saturating_mul(10u128.saturating_pow(decimals as u32 - 12)), - ) - } else { - Some( - fee_e12.saturating_div(10u128.saturating_pow(12 - decimals as u32)), - ) - }; - } else { - fee_in_asset = None - } - fee_in_asset - } - _ => None, + pub fn to_e12(amount: u128, decimals: u8) -> u128 { + if decimals > 12 { + amount.saturating_div(10u128.saturating_pow(decimals as u32 - 12)) + } else { + amount.saturating_mul(10u128.saturating_pow(12 - decimals as u32)) } } + pub fn from_e12(amount: u128, decimals: u8) -> u128 { + if decimals > 12 { + amount.saturating_mul(10u128.saturating_pow(decimals as u32 - 12)) + } else { + amount.saturating_div(10u128.saturating_pow(12 - decimals as u32)) + } + } + + pub fn convert_fee_from_pha(fee_in_pha: BalanceOf, price: u128, decimals: u8) -> u128 { + let fee_e12: u128 = fee_in_pha.into() * price / T::NativeExecutionPrice::get(); + Self::from_e12(fee_e12.into(), decimals) + } + pub fn rid_to_location(rid: &[u8; 32]) -> Result { let src_chainid: bridge::BridgeChainId = Self::get_chainid(rid); let asset_location: MultiLocation = if *rid == Self::gen_pha_rid(src_chainid) { @@ -545,16 +534,27 @@ pub mod pallet { (Concrete(location), Fungible(amount)) => { let id = T::AssetsWrapper::id(&XTransferAsset(location.clone()))?; let decimals = T::AssetsWrapper::decimals(&id).unwrap_or(12); - let amount_e12 = if decimals > 12 { - amount.saturating_div(10u128.saturating_pow(decimals as u32 - 12)) - } else { - amount.saturating_mul(10u128.saturating_pow(12 - decimals as u32)) - }; - let fee_in_pha = Self::estimate_fee_in_pha(chain_id, (amount_e12).into()); + let fee_in_pha = Self::estimate_fee_in_pha( + chain_id, + (Self::to_e12(*amount, decimals)).into(), + ); if T::NativeChecker::is_native_asset(asset) { Some(fee_in_pha.into()) } else { - Self::convert_fee_from_pha(fee_in_pha, asset) + let fee_prices = T::ExecutionPriceInfo::get(); + let fee_in_asset; + if let Some(idx) = fee_prices.iter().position(|(fee_asset_id, _)| { + fee_asset_id == &Concrete(location.clone()) + }) { + fee_in_asset = Some(Self::convert_fee_from_pha( + fee_in_pha, + fee_prices[idx].1, + decimals, + )); + } else { + fee_in_asset = None + } + fee_in_asset } } _ => None, From 344f206526a78c7e29c18d0b6f974a351ec306da Mon Sep 17 00:00:00 2001 From: Wenfeng Wang Date: Thu, 24 Feb 2022 10:52:43 +0800 Subject: [PATCH 13/13] update --- pallets/xtransfer/src/bridge_transfer/mod.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pallets/xtransfer/src/bridge_transfer/mod.rs b/pallets/xtransfer/src/bridge_transfer/mod.rs index d67cf054..b245857a 100644 --- a/pallets/xtransfer/src/bridge_transfer/mod.rs +++ b/pallets/xtransfer/src/bridge_transfer/mod.rs @@ -542,18 +542,14 @@ pub mod pallet { Some(fee_in_pha.into()) } else { let fee_prices = T::ExecutionPriceInfo::get(); - let fee_in_asset; - if let Some(idx) = fee_prices.iter().position(|(fee_asset_id, _)| { - fee_asset_id == &Concrete(location.clone()) - }) { - fee_in_asset = Some(Self::convert_fee_from_pha( - fee_in_pha, - fee_prices[idx].1, - decimals, - )); - } else { - fee_in_asset = None - } + let fee_in_asset = fee_prices + .iter() + .position(|(fee_asset_id, _)| { + fee_asset_id == &Concrete(location.clone()) + }) + .map(|idx| { + Self::convert_fee_from_pha(fee_in_pha, fee_prices[idx].1, decimals) + }); fee_in_asset } }