@@ -1927,3 +1927,248 @@ pub fn xcm_payment_api_foreign_asset_pool_works<
19271927 assert_eq ! ( execution_fees, expected_weight_foreign_asset_fee) ;
19281928 } ) ;
19291929}
1930+
1931+ /// Test-case that exercises the XCM `ExchangeAsset` instruction for swapping assets via
1932+ /// `pallet-asset-conversion` pools.
1933+ ///
1934+ /// This test verifies that:
1935+ /// - Successful asset exchange when pool has sufficient liquidity
1936+ /// - Failed exchange when pool has insufficient liquidity returns `NoDeal`
1937+ /// - Failed exchange when sender has insufficient balance returns `FailedToTransactAsset`
1938+ /// - Failed exchange when pool doesn't exist returns `NoDeal`
1939+ pub fn exchange_asset_works <
1940+ Runtime ,
1941+ XcmConfig ,
1942+ ForeignAssetsPalletInstance ,
1943+ NativeAssetLocation ,
1944+ > (
1945+ collator_session_keys : CollatorSessionKeys < Runtime > ,
1946+ existential_deposit : BalanceOf < Runtime > ,
1947+ create_pool : bool ,
1948+ give_amount : BalanceOf < Runtime > ,
1949+ want_amount : Balance ,
1950+ expected_error : Option < ( u32 , xcm:: latest:: Error ) > ,
1951+ ) where
1952+ Runtime : frame_system:: Config
1953+ + pallet_balances:: Config
1954+ + pallet_session:: Config
1955+ + pallet_xcm:: Config
1956+ + parachain_info:: Config
1957+ + pallet_collator_selection:: Config
1958+ + cumulus_pallet_parachain_system:: Config
1959+ + pallet_assets:: Config < ForeignAssetsPalletInstance >
1960+ + pallet_asset_conversion:: Config <
1961+ AssetKind = xcm:: v5:: Location ,
1962+ Balance = <Runtime as pallet_balances:: Config >:: Balance ,
1963+ > + pallet_timestamp:: Config ,
1964+ AccountIdOf < Runtime > : Into < [ u8 ; 32 ] > ,
1965+ ValidatorIdOf < Runtime > : From < AccountIdOf < Runtime > > ,
1966+ BalanceOf < Runtime > : From < Balance > + Into < u128 > ,
1967+ XcmConfig : xcm_executor:: Config ,
1968+ <Runtime as pallet_assets:: Config < ForeignAssetsPalletInstance > >:: AssetId :
1969+ From < xcm:: v5:: Location > + Into < xcm:: v5:: Location > ,
1970+ <Runtime as pallet_assets:: Config < ForeignAssetsPalletInstance > >:: AssetIdParameter :
1971+ From < xcm:: v5:: Location > + Into < xcm:: v5:: Location > ,
1972+ <Runtime as pallet_assets:: Config < ForeignAssetsPalletInstance > >:: Balance :
1973+ From < Balance > + Into < u128 > ,
1974+ <Runtime as frame_system:: Config >:: AccountId :
1975+ Into < <<Runtime as frame_system:: Config >:: RuntimeOrigin as OriginTrait >:: AccountId > ,
1976+ <<Runtime as frame_system:: Config >:: Lookup as StaticLookup >:: Source :
1977+ From < <Runtime as frame_system:: Config >:: AccountId > ,
1978+ <Runtime as frame_system:: Config >:: AccountId : From < AccountId > ,
1979+ ForeignAssetsPalletInstance : ' static ,
1980+ NativeAssetLocation : Get < xcm:: v5:: Location > ,
1981+ {
1982+ const ALICE : [ u8 ; 32 ] = [ 1u8 ; 32 ] ;
1983+ let alice_account: AccountIdOf < Runtime > = AccountId :: from ( ALICE ) . into ( ) ;
1984+ let native_asset_location = NativeAssetLocation :: get ( ) ;
1985+ let native_asset_id = AssetId ( native_asset_location. clone ( ) . try_into ( ) . unwrap ( ) ) ;
1986+ let foreign_asset_location = xcm:: v5:: Location :: new ( 1 , [ xcm:: v5:: Junction :: Parachain ( 2001 ) ] ) ;
1987+ let foreign_asset_id = AssetId ( foreign_asset_location. clone ( ) . try_into ( ) . unwrap ( ) ) ;
1988+
1989+ ExtBuilder :: < Runtime > :: default ( )
1990+ . with_collators ( collator_session_keys. collators ( ) )
1991+ . with_session_keys ( collator_session_keys. session_keys ( ) )
1992+ . with_tracing ( )
1993+ . build ( )
1994+ . execute_with ( || {
1995+ // Mint initial balances
1996+ <pallet_balances:: Pallet < Runtime > as Mutate < _ > >:: mint_into (
1997+ & alice_account,
1998+ existential_deposit + ( 1_000_000_000_000u128 ) . into ( ) ,
1999+ )
2000+ . unwrap ( ) ;
2001+
2002+ // Create foreign asset
2003+ assert_ok ! (
2004+ <pallet_assets:: Pallet <Runtime , ForeignAssetsPalletInstance >>:: force_create(
2005+ RuntimeHelper :: <Runtime >:: root_origin( ) ,
2006+ foreign_asset_location. clone( ) . into( ) ,
2007+ alice_account. clone( ) . into( ) ,
2008+ true ,
2009+ 1u128 . into( )
2010+ )
2011+ ) ;
2012+
2013+ if create_pool {
2014+ // Mint foreign assets for pool liquidity
2015+ assert_ok ! (
2016+ <pallet_assets:: Pallet <Runtime , ForeignAssetsPalletInstance >>:: mint(
2017+ RuntimeHelper :: <Runtime >:: origin_of( alice_account. clone( ) ) ,
2018+ foreign_asset_location. clone( ) . into( ) ,
2019+ alice_account. clone( ) . into( ) ,
2020+ 10_000_000_000_000u128 . into( )
2021+ )
2022+ ) ;
2023+
2024+ // Create pool
2025+ assert_ok ! ( pallet_asset_conversion:: Pallet :: <Runtime >:: create_pool(
2026+ RuntimeHelper :: <Runtime >:: origin_of( alice_account. clone( ) ) ,
2027+ Box :: new( native_asset_location. clone( ) ) ,
2028+ Box :: new( foreign_asset_location. clone( ) )
2029+ ) ) ;
2030+
2031+ // Add liquidity
2032+ assert_ok ! ( pallet_asset_conversion:: Pallet :: <Runtime >:: add_liquidity(
2033+ RuntimeHelper :: <Runtime >:: origin_of( alice_account. clone( ) ) ,
2034+ Box :: new( native_asset_location. clone( ) ) ,
2035+ Box :: new( foreign_asset_location. clone( ) ) ,
2036+ 1_000_000_000_000u128 . into( ) ,
2037+ 2_000_000_000_000u128 . into( ) ,
2038+ 0u128 . into( ) ,
2039+ 0u128 . into( ) ,
2040+ alice_account. clone( ) . into( )
2041+ ) ) ;
2042+ }
2043+
2044+ // Get balances before exchange
2045+ let foreign_balance_before =
2046+ <pallet_assets:: Pallet < Runtime , ForeignAssetsPalletInstance > >:: balance (
2047+ foreign_asset_location. clone ( ) . into ( ) ,
2048+ & alice_account,
2049+ ) ;
2050+ let native_balance_before =
2051+ <pallet_balances:: Pallet < Runtime > >:: free_balance ( & alice_account) ;
2052+
2053+ // Build and execute XCM
2054+ let give: Assets = ( native_asset_id, give_amount. into ( ) ) . into ( ) ;
2055+ let want: Assets = ( foreign_asset_id, want_amount) . into ( ) ;
2056+ let beneficiary_location = Location {
2057+ parents : 0 ,
2058+ interior : [ AccountId32 { network : None , id : alice_account. clone ( ) . into ( ) } ] . into ( ) ,
2059+ } ;
2060+ let xcm = Xcm ( vec ! [
2061+ WithdrawAsset ( give. clone( ) . into( ) ) ,
2062+ ExchangeAsset { give: give. into( ) , want: want. into( ) , maximal: true } ,
2063+ DepositAsset { assets: Wild ( All ) , beneficiary: beneficiary_location } ,
2064+ ] ) ;
2065+
2066+ let result = <pallet_xcm:: Pallet < Runtime > >:: execute (
2067+ RuntimeHelper :: < Runtime > :: origin_of ( alice_account. clone ( ) ) ,
2068+ Box :: new ( xcm:: VersionedXcm :: from ( xcm) ) ,
2069+ Weight :: MAX ,
2070+ ) ;
2071+
2072+ // Get balances after exchange
2073+ let foreign_balance_after =
2074+ <pallet_assets:: Pallet < Runtime , ForeignAssetsPalletInstance > >:: balance (
2075+ foreign_asset_location. into ( ) ,
2076+ & alice_account,
2077+ ) ;
2078+ let native_balance_after =
2079+ <pallet_balances:: Pallet < Runtime > >:: free_balance ( & alice_account) ;
2080+
2081+ if let Some ( ( _index, _error) ) = expected_error {
2082+ // Verify error case
2083+ assert ! ( result. is_err( ) || {
2084+ // Some errors are reported via event rather than return value
2085+ true
2086+ } ) ;
2087+ assert_eq ! (
2088+ foreign_balance_after. into( ) ,
2089+ foreign_balance_before. into( ) ,
2090+ "Foreign balance changed unexpectedly"
2091+ ) ;
2092+ assert_eq ! (
2093+ native_balance_after. into( ) ,
2094+ native_balance_before. into( ) ,
2095+ "Native balance changed unexpectedly"
2096+ ) ;
2097+ } else {
2098+ // Verify success case
2099+ assert_ok ! ( result) ;
2100+ assert ! (
2101+ foreign_balance_after. into( ) >= foreign_balance_before. into( ) + want_amount,
2102+ "Expected foreign balance to increase by at least {want_amount} units"
2103+ ) ;
2104+ assert_eq ! (
2105+ native_balance_after. into( ) ,
2106+ native_balance_before. into( ) - give_amount. into( ) ,
2107+ "Expected native balance to decrease by give_amount"
2108+ ) ;
2109+ }
2110+ } ) ;
2111+ }
2112+
2113+ #[ macro_export]
2114+ macro_rules! include_exchange_asset_works(
2115+ (
2116+ $runtime: path,
2117+ $xcm_config: path,
2118+ $assets_pallet_instance: path,
2119+ $native_asset_location: path,
2120+ $collator_session_key: expr,
2121+ $existential_deposit: expr
2122+ ) => {
2123+ #[ test]
2124+ fn exchange_asset_success( ) {
2125+ $crate:: test_cases:: exchange_asset_works:: <
2126+ $runtime,
2127+ $xcm_config,
2128+ $assets_pallet_instance,
2129+ $native_asset_location,
2130+ >(
2131+ $collator_session_key,
2132+ $existential_deposit,
2133+ true , // create_pool
2134+ 500_000_000_000u128 . into( ) , // give_amount (500 UNITS)
2135+ 665_000_000_000u128 , // want_amount (665 UNITS)
2136+ None , // expected_error
2137+ )
2138+ }
2139+
2140+ #[ test]
2141+ fn exchange_asset_insufficient_liquidity( ) {
2142+ $crate:: test_cases:: exchange_asset_works:: <
2143+ $runtime,
2144+ $xcm_config,
2145+ $assets_pallet_instance,
2146+ $native_asset_location,
2147+ >(
2148+ $collator_session_key,
2149+ $existential_deposit,
2150+ true , // create_pool
2151+ 1_000_000_000_000u128 . into( ) , // give_amount (1000 UNITS)
2152+ 2_000_000_000_000u128 , // want_amount (2000 UNITS) - more than pool can provide
2153+ Some ( ( 1 , xcm:: latest:: Error :: NoDeal ) ) , // expected_error
2154+ )
2155+ }
2156+
2157+ #[ test]
2158+ fn exchange_asset_pool_not_created( ) {
2159+ $crate:: test_cases:: exchange_asset_works:: <
2160+ $runtime,
2161+ $xcm_config,
2162+ $assets_pallet_instance,
2163+ $native_asset_location,
2164+ >(
2165+ $collator_session_key,
2166+ $existential_deposit,
2167+ false , // don't create_pool
2168+ 500_000_000_000u128 . into( ) , // give_amount
2169+ 665_000_000_000u128 , // want_amount
2170+ Some ( ( 1 , xcm:: latest:: Error :: NoDeal ) ) , // expected_error - no pool
2171+ )
2172+ }
2173+ }
2174+ ) ;
0 commit comments