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}