-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathlib.rs
1598 lines (1391 loc) · 57.3 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// KILT Blockchain – https://botlabs.org
// Copyright (C) 2019-2024 BOTLabs GmbH
// The KILT Blockchain is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The KILT Blockchain is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// If you feel like getting in touch with us, you can do so at info@botlabs.org
#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(any(test, feature = "runtime-benchmarks"))]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(any(feature = "try-runtime", test))]
mod try_state;
pub mod curves;
mod default_weights;
pub mod traits;
mod types;
#[cfg(feature = "runtime-benchmarks")]
pub use benchmarking::BenchmarkHelper;
pub use types::{PoolDetails, Round};
pub use default_weights::WeightInfo;
#[frame_support::pallet]
// `.expect()` is used in the macro-generated code, and we have to ignore it.
#[allow(clippy::expect_used)]
// `unreachable` is used in the macro-generated code, and we have to ignore it.
#[allow(clippy::unreachable)]
// `ref` keyword is used in the macro-generated code, and we have to ignore it.
#[allow(clippy::ref_patterns)]
// The `pallet::event` macro shadows the `deposit_event` definition of `frame_system::Config`.
// This means we cannot avoid shadow reuses anymore, and for new pallets this `allow` clause should only be added at the
// very end of the development cycle of a pallet, and from time to time it should be commented out to catch any issues
// other than the one generated by the `pallet::event` macro.
#[allow(clippy::shadow_reuse)]
pub mod pallet {
use frame_support::{
dispatch::DispatchResult,
pallet_prelude::*,
traits::{
fungible::{Inspect as InspectFungible, MutateHold},
fungibles::{
metadata::{Inspect as InspectMetadata, Mutate as MutateMetadata},
Create as CreateFungibles, Destroy as DestroyFungibles, Inspect as InspectFungibles,
Mutate as MutateFungibles,
},
tokens::{DepositConsequence, Fortitude, Precision as WithdrawalPrecision, Preservation, Provenance},
AccountTouch,
},
Hashable, Parameter,
};
use frame_system::pallet_prelude::*;
use sp_arithmetic::ArithmeticError;
use sp_core::U256;
use sp_runtime::{
traits::{
Bounded, CheckedConversion, SaturatedConversion, Saturating, StaticLookup, UniqueSaturatedInto, Zero,
},
BoundedVec, DispatchError, TokenError,
};
use sp_std::{
collections::btree_set::BTreeSet,
iter::Iterator,
ops::{AddAssign, BitOrAssign, ShlAssign},
prelude::*,
vec::Vec,
};
use substrate_fixed::{
traits::{Fixed, FixedSigned, FixedUnsigned, ToFixed},
types::I9F23,
};
use crate::{
curves::{balance_to_fixed, fixed_to_balance, BondingFunction, Curve, CurveInput},
traits::{FreezeAccounts, NextAssetIds, ResetTeam},
types::{Locks, PoolDetails, PoolManagingTeam, PoolStatus, Round, TokenMeta},
WeightInfo,
};
pub(crate) type AccountIdLookupOf<T> =
<<T as frame_system::Config>::Lookup as sp_runtime::traits::StaticLookup>::Source;
pub(crate) type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub(crate) type DepositBalanceOf<T> =
<<T as Config>::DepositCurrency as InspectFungible<<T as frame_system::Config>::AccountId>>::Balance;
pub(crate) type CollateralBalanceOf<T> =
<<T as Config>::Collaterals as InspectFungibles<<T as frame_system::Config>::AccountId>>::Balance;
pub(crate) type FungiblesBalanceOf<T> =
<<T as Config>::Fungibles as InspectFungibles<<T as frame_system::Config>::AccountId>>::Balance;
pub type FungiblesAssetIdOf<T> =
<<T as Config>::Fungibles as InspectFungibles<<T as frame_system::Config>::AccountId>>::AssetId;
pub type CollateralAssetIdOf<T> =
<<T as Config>::Collaterals as InspectFungibles<<T as frame_system::Config>::AccountId>>::AssetId;
pub(crate) type BoundedCurrencyVec<T> = BoundedVec<FungiblesAssetIdOf<T>, <T as Config>::MaxCurrenciesPerPool>;
pub(crate) type StringInputOf<T> = BoundedVec<u8, <T as Config>::MaxStringInputLength>;
pub(crate) type CurveParameterTypeOf<T> = <T as Config>::CurveParameterType;
pub(crate) type CurveParameterInputOf<T> = <T as Config>::CurveParameterInput;
pub type PoolDetailsOf<T> = PoolDetails<
<T as frame_system::Config>::AccountId,
Curve<CurveParameterTypeOf<T>>,
BoundedCurrencyVec<T>,
CollateralAssetIdOf<T>,
DepositBalanceOf<T>,
>;
/// Minimum required amount of integer and fractional bits to perform ln,
/// sqrt, and exp operations
pub(crate) type Precision = I9F23;
/// Type used for the passive supply of a pool
pub(crate) type PassiveSupply<T> = Vec<T>;
pub(crate) type TokenMetaOf<T> = TokenMeta<FungiblesBalanceOf<T>, StringInputOf<T>, StringInputOf<T>>;
pub(crate) const LOG_TARGET: &str = "runtime::pallet-bonded-coins";
/// Configure the pallet by specifying the parameters and types on which it
/// depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's
/// definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The currency used for storage deposits.
type DepositCurrency: MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
/// A fungibles trait implementation to interact with currencies which
/// can be used as collateral for minting bonded tokens.
type Collaterals: MutateFungibles<Self::AccountId>
+ AccountTouch<CollateralAssetIdOf<Self>, Self::AccountId>
+ InspectMetadata<Self::AccountId>;
/// Implementation of creating and managing new fungibles
type Fungibles: CreateFungibles<Self::AccountId>
+ DestroyFungibles<Self::AccountId>
+ MutateMetadata<Self::AccountId>
+ InspectMetadata<Self::AccountId>
+ MutateFungibles<Self::AccountId>
+ FreezeAccounts<Self::AccountId, FungiblesAssetIdOf<Self>>
+ ResetTeam<Self::AccountId>;
/// The maximum number of currencies allowed for a single pool.
#[pallet::constant]
type MaxCurrenciesPerPool: Get<u32>;
#[pallet::constant]
type MaxStringInputLength: Get<u32>;
/// The maximum denomination that bonded currencies can use. This should
/// be configured so that
/// 10^MaxDenomination < 2^CurveParameterType::frac_nbits()
/// as larger denominations could result in truncation.
#[pallet::constant]
type MaxDenomination: Get<u8>;
/// The deposit required for each bonded currency.
#[pallet::constant]
type DepositPerCurrency: Get<DepositBalanceOf<Self>>;
/// The base deposit required to create a new pool.
#[pallet::constant]
type BaseDeposit: Get<DepositBalanceOf<Self>>;
/// The origin for most permissionless and priviledged operations.
type DefaultOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
/// The dedicated origin for creating new bonded currency pools
/// (typically permissionless).
type PoolCreateOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
/// The origin for permissioned operations (force_* transactions).
type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The type used for pool ids
type PoolId: Parameter + MaxEncodedLen + From<[u8; 32]> + Into<Self::AccountId>;
type RuntimeHoldReason: From<Self::HoldReason>;
type HoldReason: TryFrom<Self::PoolId>;
/// The type used for the curve parameters. This is the type used in the
/// calculation steps and stored in the pool details.
type CurveParameterType: Parameter
+ Member
+ FixedSigned
+ MaxEncodedLen
+ PartialOrd<Precision>
+ From<Precision>;
/// Input type for curve parameters. This is the type used in the
/// extrinsic calls to create a new pool. It is converted to
/// `CurveParameterType`.
type CurveParameterInput: Parameter + FixedUnsigned + MaxEncodedLen;
type WeightInfo: WeightInfo;
type NextAssetIds: NextAssetIds<Self>;
/// Benchmark helper to calculate asset ids for the collateral and
/// bonded currencies.
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper: crate::benchmarking::BenchmarkHelper<Self>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn integrity_test() {
let scaling_factor = U256::from(10u8).checked_pow(T::MaxDenomination::get().into()).expect(
"`MaxDenomination` is set so high that the resulting scaling factor cannot be represented. /
Any attempt to mint or burn on a pool where `10^denomination > 2^256` _WILL_ fail.",
);
assert!(
U256::from(2u8).pow(T::CurveParameterType::frac_nbits().into()) > scaling_factor,
"In order to prevent truncation of balances, `MaxDenomination` should be configured such \
that the maximum scaling factor `10^MaxDenomination` is smaller than the fractional \
capacity `2^frac_nbits` of `CurveParameterType`",
);
}
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
crate::try_state::do_try_state::<T>()
}
}
#[pallet::storage]
#[pallet::getter(fn pool_with_id)]
pub type Pools<T: Config> = StorageMap<_, Twox64Concat, T::PoolId, PoolDetailsOf<T>, OptionQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Locks on a pool have been added or removed.
LockSet { id: T::PoolId, lock: Locks },
/// All locks on this pool have been cleared.
Unlocked { id: T::PoolId },
/// A new bonded token pool has been created.
PoolCreated { id: T::PoolId },
/// A bonded token pool has been moved to refunding state.
RefundingStarted { id: T::PoolId },
/// A bonded token pool has been moved to destroying state.
DestructionStarted { id: T::PoolId },
/// Collateral distribution to bonded token holders has been completed
/// for this pool (no more tokens or no more collateral to distribute).
RefundComplete { id: T::PoolId },
/// A bonded token pool has been fully destroyed and all collateral and
/// deposits have been refunded.
Destroyed { id: T::PoolId },
/// The manager of a pool has been updated.
ManagerUpdated {
id: T::PoolId,
manager: Option<T::AccountId>,
},
/// The asset managing team of a pool has been reset.
TeamChanged {
id: T::PoolId,
team: PoolManagingTeam<T::AccountId>,
},
}
#[pallet::error]
pub enum Error<T> {
/// The pool id is not currently registered.
PoolUnknown,
/// The pool has no associated bonded currency with the given index.
IndexOutOfBounds,
/// The pool does not hold collateral to be refunded, or has no
/// remaining supply of tokens to exchange. Call start_destroy to
/// intiate teardown.
NothingToRefund,
/// The user is not privileged to perform the requested operation.
NoPermission,
/// The pool is deactivated (i.e., in destroying or refunding state) and
/// not available for use.
PoolNotLive,
/// There are active accounts associated with this pool and thus it
/// cannot be destroyed at this point.
LivePool,
/// This operation can only be made when the pool is in refunding state.
NotRefunding,
/// The number of currencies linked to a pool exceeds the limit
/// parameter. Thrown by transactions that require specifying the number
/// of a pool's currencies in order to determine weight limits upfront.
CurrencyCount,
/// Raised if pool creation arguments are outside of the allowed value
/// range.
InvalidInput,
/// An internal error occurred. This should never happen.
Internal,
/// The collateral required for the minting operation exceeds the
/// provided maximum cost or the released collateral in the burning
/// operation is less than the minimum return.
Slippage,
/// The calculated collateral is zero.
ZeroCollateral,
/// A pool has to contain at least one bonded currency.
ZeroBondedCurrency,
}
#[pallet::call]
impl<T: Config> Pallet<T>
where
<CurveParameterTypeOf<T> as Fixed>::Bits:
Copy + ToFixed + AddAssign + BitOrAssign + ShlAssign + TryFrom<U256> + TryInto<U256>,
CollateralBalanceOf<T>: Into<U256> + TryFrom<U256>,
FungiblesBalanceOf<T>: Into<U256> + TryFrom<U256>,
{
/// Creates a new bonded token pool. The pool will be created with the
/// given curve, collateral currency, and bonded currencies. The pool
/// will be owned by the origin account.
///
/// # Parameters
/// - `origin`: The origin of the call.
/// - `curve`: The curve parameters for the pool.
/// - `collateral_id`: The ID of the collateral currency.
/// - `currencies`: A bounded vector of token metadata for the bonded
/// currencies. Note that no two currencies may use the same name or
/// symbol.
/// - `denomination`: The denomination for the bonded currencies.
/// - `transferable`: A boolean indicating if the bonded currencies are
/// transferable.
///
/// # Returns
/// - `DispatchResult`: The result of the dispatch.
///
/// # Errors
/// - `Error::<T>::InvalidInput`: If either
/// - the denomination is greater than the maximum allowed
/// - the curve input is invalid
/// - two currencies use the same name or symbol
/// - `Error::<T>::Internal`: If the conversion to `BoundedVec` fails.
/// - Other errors depending on the types in the config.
#[pallet::call_index(0)]
#[pallet::weight({
let currency_length = currencies.len().saturated_into();
match curve {
CurveInput::Polynomial(_) => T::WeightInfo::create_pool_polynomial(currency_length),
CurveInput::SquareRoot(_) => T::WeightInfo::create_pool_square_root(currency_length),
CurveInput::Lmsr(_) => T::WeightInfo::create_pool_lmsr(currency_length),
}
})]
pub fn create_pool(
origin: OriginFor<T>,
curve: CurveInput<CurveParameterInputOf<T>>,
collateral_id: CollateralAssetIdOf<T>,
currencies: BoundedVec<TokenMetaOf<T>, T::MaxCurrenciesPerPool>,
denomination: u8,
transferable: bool,
min_operation_balance: u128,
) -> DispatchResult {
let who = T::PoolCreateOrigin::ensure_origin(origin)?;
ensure!(denomination <= T::MaxDenomination::get(), Error::<T>::InvalidInput);
let checked_curve = curve.try_into().map_err(|_| Error::<T>::InvalidInput)?;
let currency_length = currencies.len();
ensure!(!currency_length.is_zero(), Error::<T>::ZeroBondedCurrency);
let currency_ids = T::NextAssetIds::try_get(currency_length.saturated_into())
.map_err(|e| e.into())
.and_then(|ids| -> Result<BoundedCurrencyVec<T>, DispatchError> {
if ids.len() != currency_length {
log::error!(target: LOG_TARGET, "NextAssetIds::try_get returned wrong number of ids");
return Err(Error::<T>::Internal.into());
}
BoundedCurrencyVec::<T>::try_from(ids).map_err(|_| {
log::error!(target: LOG_TARGET, "Creating boundedVec from ids failed");
Error::<T>::Internal.into()
})
})?;
let pool_id = T::PoolId::from(currency_ids.blake2_256());
if Pools::<T>::contains_key(&pool_id) {
log::error!(target: LOG_TARGET, "Attempt to create pool with pre-existing id {:?}", pool_id);
return Err(Error::<T>::Internal.into());
};
let deposit_amount = Self::calculate_pool_deposit(currency_length);
let hold_reason = Self::calculate_hold_reason(&pool_id)?;
T::DepositCurrency::hold(&hold_reason, &who, deposit_amount)?;
let pool_account = &pool_id.clone().into();
// Touch the pool account in order to be able to transfer the collateral
// currency to it. This should also verify that the currency actually exists.
T::Collaterals::touch(collateral_id.clone(), pool_account, &who)?;
// Enforce unique names and symbols by recording seen values in a set
let mut names_seen = BTreeSet::<StringInputOf<T>>::new();
let mut symbols_seen = BTreeSet::<StringInputOf<T>>::new();
currencies.into_iter().zip(currency_ids.iter()).try_for_each(
|(token_metadata, asset_id)| -> DispatchResult {
let TokenMeta {
min_balance,
name,
symbol,
} = token_metadata;
// insert() returns true if the set did not contain the inserted value
let name_ok = name.is_empty() || names_seen.insert(name.clone());
let symbol_ok = symbol.is_empty() || symbols_seen.insert(symbol.clone());
ensure!(name_ok && symbol_ok, Error::<T>::InvalidInput);
T::Fungibles::create(asset_id.clone(), pool_account.to_owned(), false, min_balance)?;
// set metadata for new asset class
T::Fungibles::set(
asset_id.to_owned(),
pool_account,
name.into_inner(),
symbol.into_inner(),
denomination,
)?;
Ok(())
},
)?;
Pools::<T>::set(
&pool_id,
Some(PoolDetails::new(
who,
checked_curve,
collateral_id,
currency_ids,
transferable,
denomination,
min_operation_balance,
deposit_amount,
)),
);
Self::deposit_event(Event::PoolCreated { id: pool_id });
Ok(())
}
/// Changes the managing team of all bonded currencies issued by this
/// pool, setting it to the provided `team`. The origin account must be
/// a manager of the pool.
///
/// # Parameters
/// - `origin`: The origin of the call, requiring the caller to be a
/// manager of the pool.
/// - `pool_id`: The identifier of the pool.
/// - `team`: The new managing team.
/// - `currency_count`: The number of bonded currencies vector linked to
/// the pool. Required for weight estimations.
///
/// # Returns
/// - `DispatchResult`: The result of the dispatch.
///
/// # Errors
/// - `Error::<T>::PoolUnknown`: If the pool does not exist.
/// - `Error::<T>::NoPermission`: If the caller is not a manager of the
/// pool.
/// - `Error::<T>::CurrencyCount`: If the actual number of currencies in
/// the pool is larger than `currency_count`.
/// - Other errors depending on the types in the config.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::reset_team())]
pub fn reset_team(
origin: OriginFor<T>,
pool_id: T::PoolId,
team: PoolManagingTeam<AccountIdOf<T>>,
currency_count: u32,
) -> DispatchResult {
let who = T::DefaultOrigin::ensure_origin(origin)?;
let pool_details = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolUnknown)?;
let number_of_currencies = Self::get_currencies_number(&pool_details);
ensure!(number_of_currencies <= currency_count, Error::<T>::CurrencyCount);
ensure!(pool_details.is_manager(&who), Error::<T>::NoPermission);
ensure!(pool_details.state.is_live(), Error::<T>::PoolNotLive);
let pool_id_account = pool_id.clone().into();
let PoolManagingTeam { ref freezer, ref admin } = team;
pool_details.bonded_currencies.into_iter().try_for_each(|asset_id| {
T::Fungibles::reset_team(
asset_id,
pool_id_account.clone(),
admin.clone(),
pool_id_account.clone(),
freezer.clone(),
)
})?;
Self::deposit_event(Event::TeamChanged { id: pool_id, team });
Ok(())
}
/// Resets the manager of a pool. The new manager will be set to the
/// provided account. If the new manager is `None`, the pool manager
/// will be cleared, after which no further privileged changes to the
/// pool can be made.
/// The origin account must be a manager of the pool.
///
/// # Parameters
/// - `origin`: The origin of the call, requiring the caller to be a
/// manager of the pool.
/// - `pool_id`: The identifier of the pool.
/// - `new_manager`: The new manager account. If `None`, the pool will
/// be set to permissionless.
///
/// # Returns
/// - `DispatchResult`: The result of the dispatch.
///
/// # Errors
/// - `Error::<T>::PoolUnknown`: If the pool does not exist.
/// - `Error::<T>::NoPermission`: If the caller is not a manager of the
/// pool.
/// - `Error::<T>::PoolNotLive`: If the pool is not in active or locked
/// state.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::reset_manager())]
pub fn reset_manager(
origin: OriginFor<T>,
pool_id: T::PoolId,
new_manager: Option<AccountIdOf<T>>,
) -> DispatchResult {
let who = T::DefaultOrigin::ensure_origin(origin)?;
Pools::<T>::try_mutate(&pool_id, |maybe_entry| -> DispatchResult {
let entry = maybe_entry.as_mut().ok_or(Error::<T>::PoolUnknown)?;
ensure!(entry.state.is_live(), Error::<T>::PoolNotLive);
ensure!(entry.is_manager(&who), Error::<T>::NoPermission);
entry.manager = new_manager.clone();
Ok(())
})?;
Self::deposit_event(Event::ManagerUpdated {
id: pool_id,
manager: new_manager,
});
Ok(())
}
/// Locks a pool. The pool will be set to a locked state with the given
/// locks. The origin account must be a manager of the pool.
/// The pool must be in a locked or active state.
/// The pool will be locked until the locks are removed.
///
/// # Parameters
/// - `origin`: The origin of the call, requiring the caller to be a
/// manager of the pool.
/// - `pool_id`: The identifier of the pool to be locked.
/// - `lock`: The locks to be applied to the pool. At least one lock
/// flag must be set to `false` for a valid lock.
///
/// # Returns
/// - `DispatchResult`: The result of the dispatch.
///
/// # Errors
/// - `Error::<T>::PoolUnknown`: If the pool does not exist.
/// - `Error::<T>::NoPermission`: If the caller is not a manager of the
/// pool.
/// - `Error::<T>::PoolNotLive`: If the pool is not in a live (locked or
/// active) state.
/// - `Error::<T>::InvalidInput`: If all lock flags are `true` (all
/// operations enabled), which would be equivalent to an unlocked
/// (active) pool state.
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::set_lock())]
pub fn set_lock(origin: OriginFor<T>, pool_id: T::PoolId, lock: Locks) -> DispatchResult {
let who = T::DefaultOrigin::ensure_origin(origin)?;
ensure!(lock.any_lock_set(), Error::<T>::InvalidInput);
Pools::<T>::try_mutate(&pool_id, |pool| -> DispatchResult {
let entry = pool.as_mut().ok_or(Error::<T>::PoolUnknown)?;
ensure!(entry.state.is_live(), Error::<T>::PoolNotLive);
ensure!(entry.is_manager(&who), Error::<T>::NoPermission);
entry.state = PoolStatus::Locked(lock.clone());
Ok(())
})?;
Self::deposit_event(Event::LockSet { id: pool_id, lock });
Ok(())
}
/// Unlocks a pool. The pool will be set to an active state. The origin
/// account must be a manager of the pool.
///
/// # Parameters
/// - `origin`: The origin of the call, requiring the caller to be a
/// manager of the pool.
/// - `pool_id`: The identifier of the pool to be unlocked.
///
/// # Returns
/// - `DispatchResult`: The result of the dispatch.
///
/// # Errors
/// - `Error::<T>::PoolUnknown`: If the pool does not exist.
/// - `Error::<T>::NoPermission`: If the caller is not a manager of the
/// pool.
/// - `Error::<T>::PoolNotLive`: If the pool is not in a live state.
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::unlock())]
pub fn unlock(origin: OriginFor<T>, pool_id: T::PoolId) -> DispatchResult {
let who = T::DefaultOrigin::ensure_origin(origin)?;
Pools::<T>::try_mutate(&pool_id, |pool| -> DispatchResult {
let entry = pool.as_mut().ok_or(Error::<T>::PoolUnknown)?;
ensure!(entry.state.is_live(), Error::<T>::PoolNotLive);
ensure!(entry.is_manager(&who), Error::<T>::NoPermission);
entry.state = PoolStatus::Active;
Ok(())
})?;
Self::deposit_event(Event::Unlocked { id: pool_id });
Ok(())
}
/// Mints new bonded tokens. The tokens will be minted into the
/// beneficiary account. In exchange, an amount of collateral determined
/// by the pool's bonding curve is debited from the caller and
/// transferred to the pool account.
/// The origin account must be a manager of the pool if its state is
/// `Locked` and `allow_mint` is false. The pool must be in a live
/// (non-refunding, non-destroying) state.
///
/// # Parameters
/// - `origin`: The origin of the call.
/// - `pool_id`: The identifier of the pool.
/// - `currency_idx`: The index of the currency in the bonded currencies
/// vector.
/// - `beneficiary`: The account to receive the minted tokens.
/// - `amount_to_mint`: The amount of bonded tokens to mint.
/// - `max_cost`: The maximum cost of collateral.
/// - `currency_count`: The maximum number of currencies allowed in the
/// pool.
///
/// # Returns
/// - `DispatchResultWithPostInfo`: The result of the dispatch with the
/// actual used weights.
///
/// # Errors
/// - `Error::<T>::PoolUnknown`: If the pool does not exist.
/// - `Error::<T>::NoPermission`: If the caller does not have permission
/// to mint.
/// - `Error::<T>::CurrencyCount`: If the number of currencies exceeds
/// `currency_count`.
/// - `Error::<T>::IndexOutOfBounds`: If the currency index is out of
/// bounds.
/// - `ArithmeticError::Overflow`: If there is an overflow during the
/// calculation.
/// - `Error::<T>::Slippage`: If the cost exceeds `max_cost`.
#[pallet::call_index(5)]
#[pallet::weight({
let weight_polynomial = T::WeightInfo::mint_into_polynomial(currency_count.to_owned());
let weight_square_root = T::WeightInfo::mint_into_square_root(currency_count.to_owned());
let weight_lmsr = T::WeightInfo::mint_into_lmsr(currency_count.to_owned());
weight_polynomial.max(weight_square_root).max(weight_lmsr)
})]
pub fn mint_into(
origin: OriginFor<T>,
pool_id: T::PoolId,
currency_idx: u32,
beneficiary: AccountIdLookupOf<T>,
amount_to_mint: FungiblesBalanceOf<T>,
max_cost: CollateralBalanceOf<T>,
currency_count: u32,
) -> DispatchResultWithPostInfo {
let who = T::DefaultOrigin::ensure_origin(origin)?;
let beneficiary = T::Lookup::lookup(beneficiary)?;
let pool_details = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolUnknown)?;
let number_of_currencies = Self::get_currencies_number(&pool_details);
ensure!(number_of_currencies <= currency_count, Error::<T>::CurrencyCount);
ensure!(
amount_to_mint >= pool_details.min_operation_balance.saturated_into(),
TokenError::BelowMinimum
);
ensure!(pool_details.state.is_live(), Error::<T>::PoolNotLive);
ensure!(pool_details.can_mint(&who), Error::<T>::NoPermission);
let bonded_currencies = pool_details.bonded_currencies;
let currency_idx: usize = currency_idx.saturated_into();
let target_currency_id = bonded_currencies
.get(currency_idx)
.ok_or(Error::<T>::IndexOutOfBounds)?;
let round_kind = Round::Up;
let (active_pre, passive) = Self::calculate_normalized_passive_issuance(
&bonded_currencies,
pool_details.denomination,
currency_idx,
round_kind,
)?;
let normalized_amount_to_mint = balance_to_fixed(
amount_to_mint.saturated_into::<u128>(),
pool_details.denomination,
round_kind,
)?;
let active_post = active_pre
.checked_add(normalized_amount_to_mint)
.ok_or(ArithmeticError::Overflow)?;
let cost = Self::calculate_collateral(
active_pre,
active_post,
passive,
&pool_details.curve,
pool_details.collateral.clone(),
round_kind,
)?;
ensure!(cost > Zero::zero(), Error::<T>::ZeroCollateral);
// fail if cost > max_cost
ensure!(cost <= max_cost, Error::<T>::Slippage);
// Transfer the collateral. We do not want to kill the minter, so this operation
// can fail if the account is being reaped.
T::Collaterals::transfer(
pool_details.collateral,
&who,
&pool_id.into(),
cost,
Preservation::Preserve,
)?;
T::Fungibles::mint_into(target_currency_id.clone(), &beneficiary, amount_to_mint)?;
if !pool_details.transferable {
T::Fungibles::freeze(target_currency_id, &beneficiary).map_err(|freeze_error| {
log::info!(target: LOG_TARGET, "Failed to freeze account: {:?}", freeze_error);
freeze_error.into()
})?;
}
Ok(Some(match pool_details.curve {
Curve::Polynomial(_) => T::WeightInfo::mint_into_polynomial(number_of_currencies),
Curve::SquareRoot(_) => T::WeightInfo::mint_into_square_root(number_of_currencies),
Curve::Lmsr(_) => T::WeightInfo::mint_into_lmsr(number_of_currencies),
})
.into())
}
/// Burns a specified amount of bonded tokens from the callers account
/// and transfers the corresponding collateral to the beneficiary.
/// The amount of collateral to be transferred is calculated based on
/// the amount of bonded tokens burned.
///
/// # Parameters
/// - `origin`: The origin of the call.
/// - `pool_id`: The identifier of the pool.
/// - `currency_idx`: The index of the currency in the bonded currencies
/// vector.
/// - `beneficiary`: The account to receive the collateral.
/// - `amount_to_burn`: The amount of bonded tokens to burn.
/// - `min_return`: The minimum amount of collateral to return.
/// - `currency_count`: The currency count in the pool, required for
/// weight calculation.
///
/// # Returns
/// - `DispatchResultWithPostInfo`: The result of the dispatch with the
/// actual used weights.
///
/// # Errors
/// - `Error::<T>::PoolUnknown`: If the pool does not exist.
/// - `Error::<T>::NoPermission`: If the caller does not have permission
/// to burn.
/// - `Error::<T>::CurrencyCount`: If the number of currencies exceeds
/// `currency_count`.
/// - `Error::<T>::IndexOutOfBounds`: If the currency index is out of
/// bounds.
/// - `ArithmeticError`: If there is an error during the calculation or
/// while converting the amount to fixed types.
/// - `Error::<T>::Slippage`: If the collateral return is less than
/// `min_return`.
#[pallet::call_index(6)]
#[pallet::weight({
let weight_polynomial = T::WeightInfo::burn_into_polynomial(currency_count.to_owned());
let weight_square_root = T::WeightInfo::burn_into_square_root(currency_count.to_owned());
let weight_lmsr = T::WeightInfo::burn_into_lmsr(currency_count.to_owned());
weight_polynomial.max(weight_square_root).max(weight_lmsr)
})]
pub fn burn_into(
origin: OriginFor<T>,
pool_id: T::PoolId,
currency_idx: u32,
beneficiary: AccountIdLookupOf<T>,
amount_to_burn: FungiblesBalanceOf<T>,
min_return: CollateralBalanceOf<T>,
currency_count: u32,
) -> DispatchResultWithPostInfo {
let who = T::DefaultOrigin::ensure_origin(origin)?;
let beneficiary = T::Lookup::lookup(beneficiary)?;
let pool_details = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolUnknown)?;
ensure!(
amount_to_burn >= pool_details.min_operation_balance.saturated_into(),
TokenError::BelowMinimum
);
let number_of_currencies = Self::get_currencies_number(&pool_details);
ensure!(number_of_currencies <= currency_count, Error::<T>::CurrencyCount);
ensure!(pool_details.state.is_live(), Error::<T>::PoolNotLive);
ensure!(pool_details.can_burn(&who), Error::<T>::NoPermission);
let bonded_currencies = pool_details.bonded_currencies;
let currency_idx: usize = currency_idx.saturated_into();
let round_kind = Round::Down;
let target_currency_id = bonded_currencies
.get(currency_idx)
.ok_or(Error::<T>::IndexOutOfBounds)?;
let (high, passive) = Self::calculate_normalized_passive_issuance(
&bonded_currencies,
pool_details.denomination,
currency_idx,
round_kind,
)?;
let normalized_amount_to_burn = balance_to_fixed(amount_to_burn, pool_details.denomination, round_kind)?;
let low = high
.checked_sub(normalized_amount_to_burn)
.ok_or(ArithmeticError::Underflow)?;
let collateral_return = Self::calculate_collateral(
low,
high,
passive,
&pool_details.curve,
pool_details.collateral.clone(),
round_kind,
)?;
ensure!(collateral_return > Zero::zero(), Error::<T>::ZeroCollateral);
ensure!(collateral_return >= min_return, Error::<T>::Slippage);
// Transfer the collateral to the beneficiary.
T::Collaterals::transfer(
pool_details.collateral,
&pool_id.into(),
&beneficiary,
collateral_return,
Preservation::Expendable,
)?;
// just remove any locks, if existing.
T::Fungibles::thaw(target_currency_id, &who).map_err(|freeze_error| {
log::info!(target: LOG_TARGET, "Failed to thaw account: {:?}", freeze_error);
// The thaw operation is failing, if there is no account to thaw. Overwrite the
// error with FungiblesError::FundsUnavailable
DispatchError::from(TokenError::FundsUnavailable)
})?;
// Burn the tokens from caller.
T::Fungibles::burn_from(
target_currency_id.clone(),
&who,
amount_to_burn,
WithdrawalPrecision::Exact,
Fortitude::Force,
)?;
let account_exists = T::Fungibles::total_balance(target_currency_id.clone(), &who) > Zero::zero();
if !pool_details.transferable && account_exists {
// Restore locks.
T::Fungibles::freeze(target_currency_id, &who).map_err(|freeze_error| {
log::info!(target: LOG_TARGET, "Failed to freeze account: {:?}", freeze_error);
freeze_error.into()
})?;
}
Ok(Some(match pool_details.curve {
Curve::Polynomial(_) => T::WeightInfo::burn_into_polynomial(number_of_currencies),
Curve::SquareRoot(_) => T::WeightInfo::burn_into_square_root(number_of_currencies),
Curve::Lmsr(_) => T::WeightInfo::burn_into_lmsr(number_of_currencies),
})
.into())
}
/// Starts the refund process for a pool. The pool will be set to a
/// refunding state. The origin account must be a manager of the pool.
///
/// # Parameters
/// - `origin`: The origin of the call, requiring the caller to be a
/// manager of the pool.
/// - `pool_id`: The identifier of the pool to start the refund process
/// for.
/// - `currency_count`: The currency count in the pool, required for
/// weight calculation.
///
/// # Returns
/// - `DispatchResultWithPostInfo`: The result of the dispatch with the
/// actual used weights.
///
/// # Errors
/// - `Error::<T>::PoolUnknown`: If the pool does not exist.
/// - `Error::<T>::CurrencyCount`: If the number of currencies exceeds
/// `max_currencies`.
/// - `Error::<T>::PoolNotLive`: If the pool is not in a live state.
/// - `Error::<T>::NoPermission`: If the caller is not a manager of the
/// pool.
/// - `Error::<T>::NothingToRefund`: If there is nothing to refund.
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::start_refund(currency_count.to_owned()))]
pub fn start_refund(
origin: OriginFor<T>,
pool_id: T::PoolId,
currency_count: u32,
) -> DispatchResultWithPostInfo {
let who = T::DefaultOrigin::ensure_origin(origin)?;
let actual_currency_count = Self::do_start_refund(pool_id, currency_count, Some(&who))?;
Ok(Some(T::WeightInfo::start_refund(actual_currency_count)).into())
}
/// Starts the refund process for a pool. The pool will be set to a
/// refunding state. The origin requires force privileges.
///
/// # Parameters
/// - `origin`: The origin of the call, requiring force privileges.
/// - `pool_id`: The identifier of the pool to start the refund process
/// for.
/// - `currency_count`: The currency count in the pool, required for
/// weight calculation.
///
/// # Returns
/// - `DispatchResultWithPostInfo`: The result of the dispatch with the
/// actual used weights.
///
/// # Errors
/// - `Error::<T>::PoolUnknown`: If the pool does not exist.
/// - `Error::<T>::CurrencyCount`: If the number of currencies exceeds
/// `max_currencies`.
/// - `Error::<T>::PoolNotLive`: If the pool is not in a live state.
/// - `Error::<T>::NoPermission`: If the caller is not a manager of the
/// pool.
/// - `Error::<T>::NothingToRefund`: If there is nothing to refund.
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::force_start_refund(currency_count.to_owned()))]
pub fn force_start_refund(
origin: OriginFor<T>,
pool_id: T::PoolId,
currency_count: u32,
) -> DispatchResultWithPostInfo {
T::ForceOrigin::ensure_origin(origin)?;