referrerpolicy=no-referrer-when-downgrade

pallet_asset_conversion/
swap.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Traits and implementations for swap between the various asset classes.
19
20use super::*;
21use frame_support::{storage::with_transaction, transactional};
22
23/// Trait for providing methods to swap between the various asset classes.
24pub trait Swap<AccountId> {
25	/// Measure units of the asset classes for swapping.
26	type Balance: Balance;
27	/// Kind of assets that are going to be swapped.
28	type AssetKind;
29
30	/// Returns the upper limit on the length of the swap path.
31	fn max_path_len() -> u32;
32
33	/// Swap exactly `amount_in` of asset `path[0]` for asset `path[last]`.
34	/// If an `amount_out_min` is specified, it will return an error if it is unable to acquire
35	/// the amount desired.
36	///
37	/// Withdraws the `path[0]` asset from `sender`, deposits the `path[last]` asset to `send_to`,
38	/// respecting `keep_alive`.
39	///
40	/// If successful, returns the amount of `path[last]` acquired for the `amount_in`.
41	///
42	/// This operation is expected to be atomic.
43	fn swap_exact_tokens_for_tokens(
44		sender: AccountId,
45		path: Vec<Self::AssetKind>,
46		amount_in: Self::Balance,
47		amount_out_min: Option<Self::Balance>,
48		send_to: AccountId,
49		keep_alive: bool,
50	) -> Result<Self::Balance, DispatchError>;
51
52	/// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[last]`. If an
53	/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
54	/// too costly.
55	///
56	/// Withdraws `path[0]` asset from `sender`, deposits `path[last]` asset to `send_to`,
57	/// respecting `keep_alive`.
58	///
59	/// If successful returns the amount of the `path[0]` taken to provide `path[last]`.
60	///
61	/// This operation is expected to be atomic.
62	fn swap_tokens_for_exact_tokens(
63		sender: AccountId,
64		path: Vec<Self::AssetKind>,
65		amount_out: Self::Balance,
66		amount_in_max: Option<Self::Balance>,
67		send_to: AccountId,
68		keep_alive: bool,
69	) -> Result<Self::Balance, DispatchError>;
70}
71
72/// Trait providing methods to swap between the various asset classes.
73pub trait SwapCredit<AccountId> {
74	/// Measure units of the asset classes for swapping.
75	type Balance: Balance;
76	/// Kind of assets that are going to be swapped.
77	type AssetKind;
78	/// Credit implying a negative imbalance in the system that can be placed into an account or
79	/// alter the total supply.
80	type Credit;
81
82	/// Returns the upper limit on the length of the swap path.
83	fn max_path_len() -> u32;
84
85	/// Swap exactly `credit_in` of asset `path[0]` for asset `path[last]`.  If `amount_out_min` is
86	/// provided and the swap can't achieve at least this amount, an error is returned.
87	///
88	/// On a successful swap, the function returns the `credit_out` of `path[last]` obtained from
89	/// the `credit_in`. On failure, it returns an `Err` containing the original `credit_in` and the
90	/// associated error code.
91	///
92	/// This operation is expected to be atomic.
93	fn swap_exact_tokens_for_tokens(
94		path: Vec<Self::AssetKind>,
95		credit_in: Self::Credit,
96		amount_out_min: Option<Self::Balance>,
97	) -> Result<Self::Credit, (Self::Credit, DispatchError)>;
98
99	/// Swaps a portion of `credit_in` of `path[0]` asset to obtain the desired `amount_out` of
100	/// the `path[last]` asset. The provided `credit_in` must be adequate to achieve the target
101	/// `amount_out`, or an error will occur.
102	///
103	/// On success, the function returns a (`credit_out`, `credit_change`) tuple, where `credit_out`
104	/// represents the acquired amount of the `path[last]` asset, and `credit_change` is the
105	/// remaining portion from the `credit_in`. On failure, an `Err` with the initial `credit_in`
106	/// and error code is returned.
107	///
108	/// This operation is expected to be atomic.
109	fn swap_tokens_for_exact_tokens(
110		path: Vec<Self::AssetKind>,
111		credit_in: Self::Credit,
112		amount_out: Self::Balance,
113	) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)>;
114}
115
116/// Trait providing methods to quote swap prices between asset classes.
117///
118/// The quoted price is only guaranteed if no other swaps are made after the price is quoted and
119/// before the target swap (e.g., the swap is made immediately within the same transaction).
120pub trait QuotePrice {
121	/// Measurement units of the asset classes for pricing.
122	type Balance: Balance;
123	/// Type representing the kind of assets for which the price is being quoted.
124	type AssetKind;
125	/// Quotes the amount of `asset1` required to obtain the exact `amount` of `asset2`.
126	///
127	/// If `include_fee` is set to `true`, the price will include the pool's fee.
128	/// If the pool does not exist or the swap cannot be made, `None` is returned.
129	fn quote_price_tokens_for_exact_tokens(
130		asset1: Self::AssetKind,
131		asset2: Self::AssetKind,
132		amount: Self::Balance,
133		include_fee: bool,
134	) -> Option<Self::Balance>;
135	/// Quotes the amount of `asset2` resulting from swapping the exact `amount` of `asset1`.
136	///
137	/// If `include_fee` is set to `true`, the price will include the pool's fee.
138	/// If the pool does not exist or the swap cannot be made, `None` is returned.
139	fn quote_price_exact_tokens_for_tokens(
140		asset1: Self::AssetKind,
141		asset2: Self::AssetKind,
142		amount: Self::Balance,
143		include_fee: bool,
144	) -> Option<Self::Balance>;
145}
146
147impl<T: Config> Swap<T::AccountId> for Pallet<T> {
148	type Balance = T::Balance;
149	type AssetKind = T::AssetKind;
150
151	fn max_path_len() -> u32 {
152		T::MaxSwapPathLength::get()
153	}
154
155	#[transactional]
156	fn swap_exact_tokens_for_tokens(
157		sender: T::AccountId,
158		path: Vec<Self::AssetKind>,
159		amount_in: Self::Balance,
160		amount_out_min: Option<Self::Balance>,
161		send_to: T::AccountId,
162		keep_alive: bool,
163	) -> Result<Self::Balance, DispatchError> {
164		Self::do_swap_exact_tokens_for_tokens(
165			sender,
166			path,
167			amount_in,
168			amount_out_min,
169			send_to,
170			keep_alive,
171		)
172	}
173
174	#[transactional]
175	fn swap_tokens_for_exact_tokens(
176		sender: T::AccountId,
177		path: Vec<Self::AssetKind>,
178		amount_out: Self::Balance,
179		amount_in_max: Option<Self::Balance>,
180		send_to: T::AccountId,
181		keep_alive: bool,
182	) -> Result<Self::Balance, DispatchError> {
183		Self::do_swap_tokens_for_exact_tokens(
184			sender,
185			path,
186			amount_out,
187			amount_in_max,
188			send_to,
189			keep_alive,
190		)
191	}
192}
193
194impl<T: Config> SwapCredit<T::AccountId> for Pallet<T> {
195	type Balance = T::Balance;
196	type AssetKind = T::AssetKind;
197	type Credit = CreditOf<T>;
198
199	fn max_path_len() -> u32 {
200		T::MaxSwapPathLength::get()
201	}
202
203	fn swap_exact_tokens_for_tokens(
204		path: Vec<Self::AssetKind>,
205		credit_in: Self::Credit,
206		amount_out_min: Option<Self::Balance>,
207	) -> Result<Self::Credit, (Self::Credit, DispatchError)> {
208		let credit_asset = credit_in.asset();
209		with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
210			let res = Self::do_swap_exact_credit_tokens_for_tokens(path, credit_in, amount_out_min);
211			match &res {
212				Ok(_) => TransactionOutcome::Commit(Ok(res)),
213				// wrapping `res` with `Ok`, since our `Err` doesn't satisfy the
214				// `From<DispatchError>` bound of the `with_transaction` function.
215				Err(_) => TransactionOutcome::Rollback(Ok(res)),
216			}
217		})
218		// should never map an error since `with_transaction` above never returns it.
219		.map_err(|_| (Self::Credit::zero(credit_asset), DispatchError::Corruption))?
220	}
221
222	fn swap_tokens_for_exact_tokens(
223		path: Vec<Self::AssetKind>,
224		credit_in: Self::Credit,
225		amount_out: Self::Balance,
226	) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)> {
227		let credit_asset = credit_in.asset();
228		with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
229			let res = Self::do_swap_credit_tokens_for_exact_tokens(path, credit_in, amount_out);
230			match &res {
231				Ok(_) => TransactionOutcome::Commit(Ok(res)),
232				// wrapping `res` with `Ok`, since our `Err` doesn't satisfy the
233				// `From<DispatchError>` bound of the `with_transaction` function.
234				Err(_) => TransactionOutcome::Rollback(Ok(res)),
235			}
236		})
237		// should never map an error since `with_transaction` above never returns it.
238		.map_err(|_| (Self::Credit::zero(credit_asset), DispatchError::Corruption))?
239	}
240}
241
242impl<T: Config> QuotePrice for Pallet<T> {
243	type Balance = T::Balance;
244	type AssetKind = T::AssetKind;
245	fn quote_price_exact_tokens_for_tokens(
246		asset1: Self::AssetKind,
247		asset2: Self::AssetKind,
248		amount: Self::Balance,
249		include_fee: bool,
250	) -> Option<Self::Balance> {
251		Self::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee)
252	}
253	fn quote_price_tokens_for_exact_tokens(
254		asset1: Self::AssetKind,
255		asset2: Self::AssetKind,
256		amount: Self::Balance,
257		include_fee: bool,
258	) -> Option<Self::Balance> {
259		Self::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee)
260	}
261}