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