1#![cfg_attr(not(feature = "std"), no_std)]
3
4use frame_support::pallet_prelude::*;
6use frame_support::traits::fungible;
7use frame_support::traits::fungibles;
8use frame_support::PalletId;
9use pallet::*;
10use sp_runtime::traits::{AccountIdConversion, CheckedDiv, CheckedMul, IntegerSquareRoot, Zero};
11
12#[cfg(test)]
15mod mock;
16
17mod liquidity_pool;
19
20
21pub use pallet::*;
23
24
25#[cfg(test)]
26mod tests;
27
28pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
30pub type AssetIdOf<T> = <<T as Config>::Fungibles as fungibles::Inspect<
31 <T as frame_system::Config>::AccountId,
32>>::AssetId;
33
34pub type BalanceOf<T> = <<T as Config>::NativeBalance as fungible::Inspect<
35 <T as frame_system::Config>::AccountId,
36>>::Balance;
37
38pub type AssetBalanceOf<T> = <<T as Config>::Fungibles as fungibles::Inspect<
39 <T as frame_system::Config>::AccountId,
40>>::Balance;
41
42
43
44#[frame_support::pallet]
46pub mod pallet {
47 use super::*;
49 use crate::liquidity_pool::LiquidityPool;
50 use frame_support::traits::fungibles::Mutate;
51 use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
52 use frame_system::pallet_prelude::*;
53
54 #[pallet::pallet]
57 pub struct Pallet<T>(_);
58
59 #[pallet::config]
61 pub trait Config: frame_system::Config {
62 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
64
65 type NativeBalance: fungible::Inspect<Self::AccountId>
67 + fungible::Mutate<Self::AccountId>
68 + fungible::hold::Inspect<Self::AccountId>
69 + fungible::hold::Mutate<Self::AccountId>
70 + fungible::freeze::Inspect<Self::AccountId>
71 + fungible::freeze::Mutate<Self::AccountId>;
72
73 type Fungibles: fungibles::Inspect<Self::AccountId, AssetId = u32>
75 + fungibles::Mutate<Self::AccountId>
76 + fungibles::Create<Self::AccountId>;
77
78 #[pallet::constant]
79 type PalletId: Get<PalletId>;
80 }
81
82 #[pallet::storage]
84 pub type LiquidityPools<T: Config> =
85 StorageMap<_, Blake2_128Concat, (AssetIdOf<T>, AssetIdOf<T>), LiquidityPool<T>>;
86
87 #[pallet::storage]
89 pub type LiquidityTokens<T: Config> =
90 StorageMap<_, Blake2_128Concat, AssetIdOf<T>, (AssetIdOf<T>, AssetIdOf<T>), ValueQuery>;
91
92 #[pallet::event]
94 #[pallet::generate_deposit(pub(super) fn deposit_event)]
95 pub enum Event<T: Config> {
96 LiquidityPoolCreated(AccountIdOf<T>, (AssetIdOf<T>, AssetIdOf<T>)),
101
102 LiquidityMinted(
108 AccountIdOf<T>,
109 (AssetIdOf<T>, AssetIdOf<T>),
110 AssetBalanceOf<T>,
111 ),
112
113 LiquidityBurned(
119 AccountIdOf<T>,
120 (AssetIdOf<T>, AssetIdOf<T>),
121 AssetBalanceOf<T>,
122 ),
123
124 Swapped(
132 AccountIdOf<T>,
133 AssetIdOf<T>,
134 AssetBalanceOf<T>,
135 AssetIdOf<T>,
136 AssetBalanceOf<T>,
137 ),
138 }
139
140 #[pallet::error]
142 pub enum Error<T> {
143 CannotHaveSameAssetInPair,
145
146 InsufficientLiquidity,
148
149 InsufficientReserves,
151
152 ReserveOverflow,
154
155 LiquidityOverflow,
157
158 InvalidAssetIn,
160
161 InvalidAssetOut,
163
164 InsufficientAmountOut,
166
167 ArithmeticOverflow,
169
170 DivisionByZero,
172
173 LiquidityPoolAlreadyExists,
175
176 LiquidityPoolNotFound,
178
179 InsufficientLiquidityMinted,
181
182 InsufficientAmountsOut,
184
185 ZeroLiquidityBurned,
187 }
188
189 #[pallet::call]
191 impl<T: Config> Pallet<T> {
192 #[pallet::call_index(0)]
194 #[pallet::weight(Weight::default())]
195 pub fn create_liquidity_pool(
196 origin: OriginFor<T>,
197 asset_a: AssetIdOf<T>,
198 asset_b: AssetIdOf<T>,
199 liquidity_token: AssetIdOf<T>,
200 ) -> DispatchResult {
201 let sender = ensure_signed(origin)?;
203
204 let trading_pair = return_vaild_pair_format::<T>(asset_a, asset_b)?;
205 ensure!(
206 !LiquidityPools::<T>::contains_key(trading_pair),
207 Error::<T>::LiquidityPoolAlreadyExists
208 );
209
210 let liquidity_pool = LiquidityPool {
212 assets: trading_pair,
213 reserves: (Zero::zero(), Zero::zero()),
214 total_liquidity: Zero::zero(),
215 liquidity_token,
216 };
217
218 LiquidityPools::<T>::insert(trading_pair, liquidity_pool);
220
221 LiquidityTokens::<T>::insert(liquidity_token, trading_pair);
223
224
225 Self::deposit_event(Event::LiquidityPoolCreated(sender, trading_pair));
227
228 Ok(())
229 }
230
231 #[pallet::call_index(1)]
232 #[pallet::weight(Weight::default())]
233 pub fn mint_liquidity(
234 origin: OriginFor<T>,
235 asset_a: AssetIdOf<T>,
236 asset_b: AssetIdOf<T>,
237 amount_a: AssetBalanceOf<T>,
238 amount_b: AssetBalanceOf<T>,
239 min_liquidity: AssetBalanceOf<T>,
240 ) -> DispatchResult {
241 let sender = ensure_signed(origin)?;
242
243 let trading_pair = return_vaild_pair_format::<T>(asset_a, asset_b)?;
244
245 let mut liquidity_pool =
247 LiquidityPools::<T>::get(&trading_pair).ok_or(Error::<T>::LiquidityPoolNotFound)?;
248
249 let liquidity_minted = Self::calculate_liquidity_minted(
251 (amount_a, amount_b),
252 (liquidity_pool.reserves.0, liquidity_pool.reserves.1),
253 liquidity_pool.total_liquidity,
254 )?;
255
256 ensure!(
258 liquidity_minted >= min_liquidity,
259 Error::<T>::InsufficientLiquidityMinted
260 );
261
262 Self::transfer_asset_to_pool(&sender, trading_pair.0, amount_a)?;
264 Self::transfer_asset_to_pool(&sender, trading_pair.1, amount_b)?;
265
266 Self::mint_liquidity_tokens(&sender, liquidity_pool.liquidity_token, liquidity_minted)?;
268
269 liquidity_pool.mint((amount_a, amount_b), liquidity_minted)?;
271
272 LiquidityPools::<T>::insert(&trading_pair, liquidity_pool);
274
275 Self::deposit_event(Event::LiquidityMinted(
277 sender,
278 trading_pair,
279 liquidity_minted,
280 ));
281
282 Ok(())
283 }
284
285 #[pallet::call_index(2)]
287 #[pallet::weight(Weight::default())]
288 pub fn burn_liquidity(
289 origin: OriginFor<T>,
290 asset_a: AssetIdOf<T>,
291 asset_b: AssetIdOf<T>,
292 liquidity_burned: AssetBalanceOf<T>,
293 min_amount_a: AssetBalanceOf<T>,
294 min_amount_b: AssetBalanceOf<T>,
295 ) -> DispatchResult {
296 let sender = ensure_signed(origin)?;
297
298 let trading_pair = return_vaild_pair_format::<T>(asset_a, asset_b)?;
299
300 let mut liquidity_pool =
301 LiquidityPools::<T>::get(trading_pair).ok_or(Error::<T>::LiquidityPoolNotFound)?;
302
303 let amounts_out = Self::calculate_amounts_out(
306 liquidity_burned,
307 (liquidity_pool.reserves.0, liquidity_pool.reserves.1),
308 liquidity_pool.total_liquidity,
309 )?;
310 ensure!(
311 amounts_out.0 >= min_amount_a && amounts_out.1 >= min_amount_b,
312 Error::<T>::InsufficientAmountsOut
313 );
314
315 Self::burn_liquidity_tokens(&sender, liquidity_pool.liquidity_token, liquidity_burned)?;
317
318 liquidity_pool.burn(liquidity_burned, amounts_out)?;
320 LiquidityPools::<T>::insert(trading_pair, liquidity_pool);
321
322 Self::transfer_asset_to_user(&sender, trading_pair.0, amounts_out.0)?;
324 Self::transfer_asset_to_user(&sender, trading_pair.1, amounts_out.1)?;
325
326 Self::deposit_event(Event::LiquidityBurned(
327 sender,
328 trading_pair,
329 liquidity_burned,
330 ));
331
332 Ok(())
333 }
334
335 #[pallet::call_index(3)]
336 #[pallet::weight(Weight::default())]
337 pub fn swap(
338 origin: OriginFor<T>,
339 asset_in: AssetIdOf<T>,
340 asset_out: AssetIdOf<T>,
341 amount_in: AssetBalanceOf<T>,
342 min_amount_out: AssetBalanceOf<T>,
343 ) -> DispatchResult {
344 let sender = ensure_signed(origin)?;
345
346 let (liquidity_pool, trading_pair) = {
347 let trading_pair = return_vaild_pair_format::<T>(asset_in, asset_out)?;
348 (LiquidityPools::<T>::get(&trading_pair), trading_pair)
349 };
350
351 let mut liquidity_pool = liquidity_pool.ok_or(Error::<T>::LiquidityPoolNotFound)?;
352
353 let amount_out = liquidity_pool.swap(asset_in, asset_out, amount_in, min_amount_out)?;
354
355 Self::transfer_asset_from_user(&sender, asset_in, amount_in)?;
356 Self::transfer_asset_to_user(&sender, asset_out, amount_out)?;
357
358 LiquidityPools::<T>::insert(&trading_pair, liquidity_pool);
359
360 Self::deposit_event(Event::Swapped(
361 sender, asset_in, amount_in, asset_out, amount_out,
362 ));
363
364 Ok(())
365 }
366 }
367
368 impl<T: Config> Pallet<T> {
370 fn calculate_liquidity_minted(
371 amounts: (AssetBalanceOf<T>, AssetBalanceOf<T>),
372 reserves: (AssetBalanceOf<T>, AssetBalanceOf<T>),
373 total_liquidity: AssetBalanceOf<T>,
374 ) -> Result<AssetBalanceOf<T>, DispatchError> {
375 let (amount_a, amount_b) = amounts;
376 let (reserve_a, reserve_b) = reserves;
377
378 ensure!(
379 !amount_a.is_zero() && !amount_b.is_zero(),
380 Error::<T>::InsufficientLiquidityMinted
381 );
382
383 if total_liquidity.is_zero() {
384 let liquidity_minted = Self::geometric_mean(amount_a, amount_b)?;
386 Ok(liquidity_minted)
387 } else {
388 let liquidity_minted_a = amount_a
390 .checked_mul(&total_liquidity)
391 .ok_or(Error::<T>::ArithmeticOverflow)?
392 .checked_div(&reserve_a)
393 .ok_or(Error::<T>::DivisionByZero)?;
394
395 let liquidity_minted_b = amount_b
396 .checked_mul(&total_liquidity)
397 .ok_or(Error::<T>::ArithmeticOverflow)?
398 .checked_div(&reserve_b)
399 .ok_or(Error::<T>::DivisionByZero)?;
400
401 let liquidity_minted = sp_std::cmp::min(liquidity_minted_a, liquidity_minted_b);
403 Ok(liquidity_minted)
404 }
405 }
406
407 fn geometric_mean(
409 amount_a: AssetBalanceOf<T>,
410 amount_b: AssetBalanceOf<T>,
411 ) -> Result<AssetBalanceOf<T>, DispatchError> {
412 let sqrt_product = (amount_a
413 .checked_mul(&amount_b)
414 .ok_or(Error::<T>::ArithmeticOverflow)?)
415 .integer_sqrt();
416 Ok(sqrt_product)
417 }
418
419 fn pallet_account_id() -> T::AccountId {
420 T::PalletId::get().into_account_truncating()
421 }
422
423 fn transfer_asset_to_pool(
424 sender: &AccountIdOf<T>,
425 asset_id: AssetIdOf<T>,
426 amount: AssetBalanceOf<T>,
427 ) -> DispatchResult {
428 T::Fungibles::transfer(
430 asset_id,
431 sender,
432 &Self::pallet_account_id(),
433 amount,
434 Preservation::Expendable,
435 )?;
436
437 Ok(())
438 }
439
440 fn mint_liquidity_tokens(
441 recipient: &AccountIdOf<T>,
442 liquidity_token_id: AssetIdOf<T>,
443 amount: AssetBalanceOf<T>,
444 ) -> DispatchResult {
445 T::Fungibles::mint_into(liquidity_token_id, recipient, amount)?;
447 Ok(())
448 }
449
450 fn burn_liquidity_tokens(
451 sender: &AccountIdOf<T>,
452 liquidity_token_id: AssetIdOf<T>,
453 amount: AssetBalanceOf<T>,
454 ) -> DispatchResult {
455 T::Fungibles::burn_from(
457 liquidity_token_id,
458 sender,
459 amount,
460 Precision::Exact,
461 Fortitude::Polite,
462 )?;
463 Ok(())
464 }
465
466 fn calculate_amounts_out(
467 liquidity_burned: AssetBalanceOf<T>,
468 reserves: (AssetBalanceOf<T>, AssetBalanceOf<T>),
469 total_liquidity: AssetBalanceOf<T>,
470 ) -> Result<(AssetBalanceOf<T>, AssetBalanceOf<T>), DispatchError> {
471 ensure!(!liquidity_burned.is_zero(), Error::<T>::ZeroLiquidityBurned);
472 ensure!(
473 !total_liquidity.is_zero(),
474 Error::<T>::InsufficientLiquidity
475 );
476
477 let (reserve_a, reserve_b) = reserves;
478 ensure!(
479 !reserve_a.is_zero() && !reserve_b.is_zero(),
480 Error::<T>::InsufficientLiquidity
481 );
482
483 let amount_a = liquidity_burned
484 .checked_mul(&reserve_a)
485 .ok_or(Error::<T>::ArithmeticOverflow)?
486 .checked_div(&total_liquidity)
487 .ok_or(Error::<T>::DivisionByZero)?;
488
489 let amount_b = liquidity_burned
490 .checked_mul(&reserve_b)
491 .ok_or(Error::<T>::ArithmeticOverflow)?
492 .checked_div(&total_liquidity)
493 .ok_or(Error::<T>::DivisionByZero)?;
494
495 Ok((amount_a, amount_b))
496 }
497
498 fn transfer_asset_from_user(
499 user: &AccountIdOf<T>,
500 asset_id: AssetIdOf<T>,
501 amount: AssetBalanceOf<T>,
502 ) -> DispatchResult {
503 T::Fungibles::transfer(
504 asset_id,
505 user,
506 &Self::pallet_account_id(),
507 amount,
508 Preservation::Expendable,
509 )?;
510 Ok(())
511 }
512
513 fn transfer_asset_to_user(
514 user: &AccountIdOf<T>,
515 asset_id: AssetIdOf<T>,
516 amount: AssetBalanceOf<T>,
517 ) -> DispatchResult {
518 T::Fungibles::transfer(
519 asset_id,
520 &Self::pallet_account_id(),
521 user,
522 amount,
523 Preservation::Expendable,
524 )?;
525 Ok(())
526 }
527 }
528}
529
530
531
532fn return_vaild_pair_format<T: pallet::Config>(asset_a: AssetIdOf<T>, asset_b: AssetIdOf<T>) -> Result<(AssetIdOf<T>, AssetIdOf<T>), DispatchError> {
533 if asset_a == asset_b {
534 return Err(Error::<T>::LiquidityPoolAlreadyExists.into());
535 }
536
537 if asset_a < asset_b {
538 Ok((asset_a, asset_b))
539 } else {
540 Ok((asset_b, asset_a))
541 }
542}