referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/asset_exchange/single_asset_adapter/
adapter.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Single asset exchange adapter.
18
19extern crate alloc;
20use alloc::vec;
21use core::marker::PhantomData;
22use frame_support::{ensure, traits::tokens::fungibles};
23use pallet_asset_conversion::{QuotePrice, SwapCredit};
24use xcm::prelude::*;
25use xcm_executor::{
26	traits::{AssetExchange, MatchesFungibles},
27	AssetsInHolding,
28};
29
30/// An adapter from [`pallet_asset_conversion::SwapCredit`] and
31/// [`pallet_asset_conversion::QuotePrice`] to [`xcm_executor::traits::AssetExchange`].
32///
33/// This adapter takes just one fungible asset in `give` and allows only one fungible asset in
34/// `want`. If you need to handle more assets in either `give` or `want`, then you should use
35/// another type that implements [`xcm_executor::traits::AssetExchange`] or build your own.
36///
37/// This adapter also only works for fungible assets.
38///
39/// `exchange_asset` and `quote_exchange_price` will both return an error if there's
40/// more than one asset in `give` or `want`.
41pub struct SingleAssetExchangeAdapter<AssetConversion, Fungibles, Matcher, AccountId>(
42	PhantomData<(AssetConversion, Fungibles, Matcher, AccountId)>,
43);
44impl<AssetConversion, Fungibles, Matcher, AccountId> AssetExchange
45	for SingleAssetExchangeAdapter<AssetConversion, Fungibles, Matcher, AccountId>
46where
47	AssetConversion: SwapCredit<
48			AccountId,
49			Balance = u128,
50			AssetKind = Fungibles::AssetId,
51			Credit = fungibles::Credit<AccountId, Fungibles>,
52		> + QuotePrice<Balance = u128, AssetKind = Fungibles::AssetId>,
53	Fungibles: fungibles::Balanced<AccountId, Balance = u128>,
54	Matcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
55{
56	fn exchange_asset(
57		_: Option<&Location>,
58		give: AssetsInHolding,
59		want: &Assets,
60		maximal: bool,
61	) -> Result<AssetsInHolding, AssetsInHolding> {
62		let mut give_iter = give.fungible_assets_iter();
63		let give_asset = give_iter.next().ok_or_else(|| {
64			tracing::trace!(
65				target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
66				?give, "No fungible asset was in `give`.",
67			);
68			give.clone()
69		})?;
70		ensure!(give_iter.next().is_none(), give.clone()); // We only support 1 asset in `give`.
71		ensure!(give.non_fungible_assets_iter().next().is_none(), give.clone()); // We don't allow non-fungible assets.
72		ensure!(want.len() == 1, give.clone()); // We only support 1 asset in `want`.
73		let want_asset = want.get(0).ok_or_else(|| give.clone())?;
74		let (give_asset_id, give_amount) =
75			Matcher::matches_fungibles(&give_asset).map_err(|error| {
76				tracing::trace!(
77					target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
78					?give_asset,
79					?error,
80					"Could not map XCM asset give to FRAME asset.",
81				);
82				give.clone()
83			})?;
84		let (want_asset_id, want_amount) =
85			Matcher::matches_fungibles(&want_asset).map_err(|error| {
86				tracing::trace!(
87					target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
88					?want_asset,
89					?error,
90					"Could not map XCM asset want to FRAME asset."
91				);
92				give.clone()
93			})?;
94
95		// We have to do this to convert the XCM assets into credit the pool can use.
96		let swap_asset = give_asset_id.clone().into();
97		let credit_in = Fungibles::issue(give_asset_id, give_amount);
98
99		// Do the swap.
100		let (credit_out, maybe_credit_change) = if maximal {
101			// If `maximal`, then we swap exactly `credit_in` to get as much of `want_asset_id` as
102			// we can, with a minimum of `want_amount`.
103			let credit_out = <AssetConversion as SwapCredit<_>>::swap_exact_tokens_for_tokens(
104				vec![swap_asset, want_asset_id],
105				credit_in,
106				Some(want_amount),
107			)
108			.map_err(|(credit_in, error)| {
109				tracing::debug!(
110					target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
111					?error,
112					"Could not perform the swap"
113				);
114				drop(credit_in);
115				give.clone()
116			})?;
117
118			// We don't have leftover assets if exchange was maximal.
119			(credit_out, None)
120		} else {
121			// If `minimal`, then we swap as little of `credit_in` as we can to get exactly
122			// `want_amount` of `want_asset_id`.
123			let (credit_out, credit_change) =
124				<AssetConversion as SwapCredit<_>>::swap_tokens_for_exact_tokens(
125					vec![swap_asset, want_asset_id],
126					credit_in,
127					want_amount,
128				)
129				.map_err(|(credit_in, error)| {
130					tracing::debug!(
131						target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
132						?error,
133						"Could not perform the swap",
134					);
135					drop(credit_in);
136					give.clone()
137				})?;
138
139			(credit_out, if credit_change.peek() > 0 { Some(credit_change) } else { None })
140		};
141
142		// We create an `AssetsInHolding` instance by putting in the resulting asset
143		// of the exchange.
144		let resulting_asset: Asset = (want_asset.id.clone(), credit_out.peek()).into();
145		let mut result: AssetsInHolding = resulting_asset.into();
146
147		// If we have some leftover assets from the exchange, also put them in the result.
148		if let Some(credit_change) = maybe_credit_change {
149			let leftover_asset: Asset = (give_asset.id.clone(), credit_change.peek()).into();
150			result.subsume(leftover_asset);
151		}
152
153		Ok(result.into())
154	}
155
156	fn quote_exchange_price(give: &Assets, want: &Assets, maximal: bool) -> Option<Assets> {
157		if give.len() != 1 || want.len() != 1 {
158			return None;
159		} // We only support 1 asset in `give` or `want`.
160		let give_asset = give.get(0)?;
161		let want_asset = want.get(0)?;
162		// We first match both XCM assets to the asset ID types `AssetConversion` can handle.
163		let (give_asset_id, give_amount) = Matcher::matches_fungibles(give_asset)
164			.map_err(|error| {
165				tracing::trace!(
166					target: "xcm::SingleAssetExchangeAdapter::quote_exchange_price",
167					?give_asset,
168					?error,
169					"Could not map XCM asset to FRAME asset."
170				);
171				()
172			})
173			.ok()?;
174		let (want_asset_id, want_amount) = Matcher::matches_fungibles(want_asset)
175			.map_err(|error| {
176				tracing::trace!(
177					target: "xcm::SingleAssetExchangeAdapter::quote_exchange_price",
178					?want_asset,
179					?error,
180					"Could not map XCM asset to FRAME asset"
181				);
182				()
183			})
184			.ok()?;
185		// We quote the price.
186		if maximal {
187			// The amount of `want` resulting from swapping `give`.
188			let resulting_want =
189				<AssetConversion as QuotePrice>::quote_price_exact_tokens_for_tokens(
190					give_asset_id,
191					want_asset_id,
192					give_amount,
193					true, // Include fee.
194				)?;
195
196			Some((want_asset.id.clone(), resulting_want).into())
197		} else {
198			// The `give` amount required to obtain `want`.
199			let necessary_give =
200				<AssetConversion as QuotePrice>::quote_price_tokens_for_exact_tokens(
201					give_asset_id,
202					want_asset_id,
203					want_amount,
204					true, // Include fee.
205				)?;
206
207			Some((give_asset.id.clone(), necessary_give).into())
208		}
209	}
210}