diff --git a/Cargo.lock b/Cargo.lock index 5a01e8b8b..f5939f782 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4571,7 +4571,7 @@ dependencies = [ [[package]] name = "hydradx-adapters" -version = "1.2.1" +version = "1.2.2" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", @@ -4621,7 +4621,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "217.0.0" +version = "218.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -11361,7 +11361,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.19.3" +version = "1.19.4" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index ec378e6a4..c0679008c 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.19.3" +version = "1.19.4" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" diff --git a/integration-tests/src/cross_chain_transfer.rs b/integration-tests/src/cross_chain_transfer.rs index 9bc35fe40..29ef8bc83 100644 --- a/integration-tests/src/cross_chain_transfer.rs +++ b/integration-tests/src/cross_chain_transfer.rs @@ -11,6 +11,7 @@ use hex_literal::hex; use hydradx_traits::registry::Mutate; use orml_traits::currency::MultiCurrency; use pretty_assertions::assert_eq; +use primitives::AccountId; use sp_core::H256; use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash}; use xcm_emulator::TestExt; @@ -149,6 +150,130 @@ fn hydra_should_receive_asset_when_transferred_from_acala() { }); } +#[test] +fn hydra_should_receive_asset_when_transferred_from_acala_to_eth_address() { + // Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::set_location( + ACA, + hydradx_runtime::AssetLocation(MultiLocation::new(1, X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)))) + )); + }); + + let amount = 30 * UNITS; + Acala::execute_with(|| { + //We send to ethereum address with Account20 + assert_ok!(hydradx_runtime::XTokens::transfer( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + 0, + amount, + Box::new( + MultiLocation::new( + 1, + X2( + Junction::Parachain(HYDRA_PARA_ID), + Junction::AccountKey20 { + network: None, + key: evm_address().into(), + } + ) + ) + .into() + ), + WeightLimit::Limited(Weight::from_parts(399_600_000_000, 0)) + )); + // Assert + assert_eq!( + hydradx_runtime::Balances::free_balance(&AccountId::from(ALICE)), + ALICE_INITIAL_NATIVE_BALANCE - amount + ); + }); + + Hydra::execute_with(|| { + let fee = hydradx_runtime::Tokens::free_balance(ACA, &hydradx_runtime::Treasury::account_id()); + assert!(fee > 0, "fee should be greater than 0"); + assert_eq!( + hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(evm_account())), + amount - fee + ); + }); +} + +#[test] +fn hydra_should_receive_asset_when_transferred_from_acala_to_same_address_represented_as_both_account32_and_20() { + // Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::set_location( + ACA, + hydradx_runtime::AssetLocation(MultiLocation::new(1, X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)))) + )); + }); + + let amount = 30 * UNITS; + Acala::execute_with(|| { + //We send to ethereum address with Account20 + assert_ok!(hydradx_runtime::XTokens::transfer( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + 0, + amount, + Box::new( + MultiLocation::new( + 1, + X2( + Junction::Parachain(HYDRA_PARA_ID), + Junction::AccountKey20 { + network: None, + key: evm_address().into(), + } + ) + ) + .into() + ), + WeightLimit::Limited(Weight::from_parts(399_600_000_000, 0)) + )); + + //We send it again to the same address, but to normal Account32 + assert_ok!(hydradx_runtime::XTokens::transfer( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + 0, + amount, + Box::new( + MultiLocation::new( + 1, + X2( + Junction::Parachain(HYDRA_PARA_ID), + Junction::AccountId32 { + id: evm_account().into(), + network: None + } + ) + ) + .into() + ), + WeightLimit::Limited(Weight::from_parts(399_600_000_000, 0)) + )); + + // Assert + assert_eq!( + hydradx_runtime::Balances::free_balance(&AccountId::from(ALICE)), + ALICE_INITIAL_NATIVE_BALANCE - 2 * amount + ); + }); + + Hydra::execute_with(|| { + let fee_2x = hydradx_runtime::Tokens::free_balance(ACA, &hydradx_runtime::Treasury::account_id()); + assert!(fee_2x > 0, "fee should be greater than 0"); + assert_eq!( + hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(evm_account())), + 2 * amount - fee_2x + ); + }); +} + #[test] fn transfer_from_acala_should_fail_when_transferring_insufficient_amount() { TestNet::reset(); diff --git a/integration-tests/src/global_account_derivation.rs b/integration-tests/src/global_account_derivation.rs new file mode 100644 index 000000000..954037063 --- /dev/null +++ b/integration-tests/src/global_account_derivation.rs @@ -0,0 +1,124 @@ +#![cfg(test)] +use crate::polkadot_test_net::*; + +use frame_support::assert_ok; +use sp_runtime::codec::Encode; + +use frame_support::dispatch::GetDispatchInfo; +use orml_traits::MultiCurrency; +use polkadot_xcm::latest::prelude::*; +use xcm_builder::DescribeAllTerminal; +use xcm_builder::DescribeFamily; +use xcm_builder::HashedDescription; +use xcm_emulator::ConvertLocation; +use xcm_emulator::TestExt; +#[test] +fn other_chain_remote_account_should_work_on_hydra() { + // Arrange + TestNet::reset(); + + let xcm_interior_at_acala = X1(Junction::AccountId32 { + network: None, + id: evm_account().into(), + }); + + let xcm_origin_at_hydra = MultiLocation { + parents: 1, + interior: X2( + Junction::Parachain(ACALA_PARA_ID), + Junction::AccountId32 { + network: None, + id: evm_account().into(), + }, + ), + }; + + let acala_account_id_at_hydra: AccountId = + HashedDescription::>::convert_location(&xcm_origin_at_hydra) + .unwrap(); + + Hydra::execute_with(|| { + init_omnipool(); + + assert_ok!(hydradx_runtime::Balances::transfer( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + acala_account_id_at_hydra.clone(), + 1_000 * UNITS, + )); + + assert_eq!( + hydradx_runtime::Currencies::free_balance(DAI, &AccountId::from(acala_account_id_at_hydra.clone())), + 0 + ); + }); + + // Act + Acala::execute_with(|| { + let omni_sell = + hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { + asset_in: HDX, + asset_out: DAI, + amount: UNITS, + min_buy_amount: 0, + }); + + let message = Xcm(vec![ + WithdrawAsset( + ( + MultiLocation { + parents: 1, + interior: X2(Parachain(HYDRA_PARA_ID), GeneralIndex(0)), + }, + 900 * UNITS, + ) + .into(), + ), + BuyExecution { + fees: ( + MultiLocation { + parents: 1, + interior: X2(Parachain(HYDRA_PARA_ID), GeneralIndex(0)), + }, + 800 * UNITS, + ) + .into(), + weight_limit: Unlimited, + }, + Transact { + require_weight_at_most: omni_sell.get_dispatch_info().weight, + origin_kind: OriginKind::SovereignAccount, + call: omni_sell.encode().into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + RefundSurplus, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { + id: acala_account_id_at_hydra.clone().into(), + network: None, + } + .into(), + }, + ]); + + assert_ok!(hydradx_runtime::PolkadotXcm::send_xcm( + xcm_interior_at_acala, + MultiLocation::new(1, X1(Parachain(HYDRA_PARA_ID))), + message + )); + }); + + // Assert + Hydra::execute_with(|| { + assert!(hydradx_runtime::System::events().iter().any(|r| matches!( + r.event, + hydradx_runtime::RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::Success { .. }) + ))); + + let dai_balance = hydradx_runtime::Currencies::free_balance(DAI, &AccountId::from(acala_account_id_at_hydra)); + assert!( + dai_balance > 0, + "Omnipool sell swap failed as the user did not receive any DAI" + ); + }); +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index d634a9e2d..c5fe26fc2 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -12,6 +12,7 @@ mod dynamic_fees; mod evm; mod exchange_asset; mod fee_calculation; +mod global_account_derivation; mod insufficient_assets_ed; mod non_native_fee; mod omnipool_init; diff --git a/runtime/adapters/Cargo.toml b/runtime/adapters/Cargo.toml index 3debce6f5..cfd02822d 100644 --- a/runtime/adapters/Cargo.toml +++ b/runtime/adapters/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-adapters" -version = "1.2.1" +version = "1.2.2" description = "Structs and other generic types for building runtimes." authors = ["GalacticCouncil"] edition = "2021" diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 862accf10..5e8059e96 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "217.0.0" +version = "218.0.0" authors = ["GalacticCouncil"] edition = "2021" license = "Apache 2.0" @@ -112,6 +112,7 @@ polkadot-xcm = { workspace = true } xcm-executor = { workspace = true } xcm-builder = { workspace = true } + # Substrate dependencies frame-benchmarking = { workspace = true, optional = true } frame-executive = { workspace = true } diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 9a7e7d81b..3947de413 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -107,7 +107,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 217, + spec_version: 218, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index f9d97dad7..3289ed7e9 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -1,4 +1,5 @@ use super::*; +use sp_std::marker::PhantomData; use codec::MaxEncodedLen; use hydradx_adapters::{ @@ -19,6 +20,7 @@ use hydradx_adapters::xcm_exchange::XcmAssetExchanger; use hydradx_adapters::xcm_execute_filter::AllowTransferAndSwap; use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiNativeAsset}; +use pallet_evm::AddressMapping; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::{RelayChainBlockNumber, Sibling}; use polkadot_xcm::v3::{prelude::*, Weight as XcmWeight}; @@ -26,9 +28,9 @@ use primitives::Price; use scale_info::TypeInfo; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - EnsureXcmOrigin, FixedWeightBounds, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, WithComputedOrigin, + DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FixedWeightBounds, HashedDescription, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, WithComputedOrigin, }; use xcm_executor::{Config, XcmExecutor}; @@ -281,6 +283,7 @@ impl pallet_xcm_rate_limiter::Config for Runtime { } pub struct CurrencyIdConvert; +use crate::evm::ExtendedAddressMapping; use primitives::constants::chain::CORE_ASSET_ID; impl Convert> for CurrencyIdConvert { @@ -359,7 +362,29 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Generate remote accounts according to polkadot standards + HashedDescription>, + // Convert ETH to local substrate account + EvmAddressConversion, ); +use xcm_executor::traits::ConvertLocation; + +/// Converts Account20 (ethereum) addresses to AccountId32 (substrate) addresses. +pub struct EvmAddressConversion(PhantomData); +impl>> ConvertLocation for EvmAddressConversion { + fn convert_location(location: &MultiLocation) -> Option { + match location { + MultiLocation { + parents: 0, + interior: X1(AccountKey20 { network: _, key }), + } => { + let account_32 = ExtendedAddressMapping::into_account_id(H160::from(key)); + Some(account_32) + } + _ => None, + } + } +} parameter_types! { // The account which receives multi-currency tokens from failed attempts to deposit them