forked from AcalaNetwork/Acala
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
442 lines (382 loc) · 16 KB
/
lib.rs
File metadata and controls
442 lines (382 loc) · 16 KB
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
//! # CDP Treasury Module
//!
//! ## Overview
//!
//! CDP Treasury manages the accumulated interest and bad debts generated by CDPs,
//! and handle excessive surplus or debits timely in order to keep the system healthy with low risk.
//! It's the only entry for issuing/burning stable coin for whole system.
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
decl_error, decl_event, decl_module, decl_storage, ensure,
traits::{EnsureOrigin, Get},
weights::{constants::WEIGHT_PER_MICROS, DispatchClass},
};
use frame_system::{self as system};
use orml_traits::{MultiCurrency, MultiCurrencyExtended};
use orml_utilities::with_transaction_result;
use primitives::{Balance, CurrencyId};
use sp_runtime::{
traits::{AccountIdConversion, Zero},
DispatchError, DispatchResult, FixedPointNumber, ModuleId,
};
use support::{AuctionManager, CDPTreasury, CDPTreasuryExtended, DEXManager, OnEmergencyShutdown, Ratio};
mod benchmarking;
mod mock;
mod tests;
pub trait Trait: system::Trait {
type Event: From<Event> + Into<<Self as system::Trait>::Event>;
/// The origin which may update parameters. Root can always do this.
type UpdateOrigin: EnsureOrigin<Self::Origin>;
/// The Currency for managing assets related to CDP
type Currency: MultiCurrencyExtended<Self::AccountId, CurrencyId = CurrencyId, Balance = Balance>;
/// Stablecoin currency id
type GetStableCurrencyId: Get<CurrencyId>;
/// Auction manager creates different types of auction to handle system surplus and debit, and confiscated collateral assets
type AuctionManagerHandler: AuctionManager<Self::AccountId, CurrencyId = CurrencyId, Balance = Balance>;
/// Dex manager is used to swap confiscated collateral assets to stable coin
type DEX: DEXManager<Self::AccountId, CurrencyId, Balance>;
/// the cap of lots number when create collateral auction on a liquidation
/// or to create debit/surplus auction on block end.
/// If set to 0, does not work.
type MaxAuctionsCount: Get<u32>;
/// The CDP treasury's module id, keep surplus and collateral assets from liquidation.
type ModuleId: Get<ModuleId>;
}
decl_event!(
pub enum Event {
/// The fixed size for surplus auction updated. [new_size]
SurplusAuctionFixedSizeUpdated(Balance),
/// The buffer size of surplus pool updated. [new_size]
SurplusBufferSizeUpdated(Balance),
/// The initial supply amount of a debit auction updated. [new_amount]
InitialAmountPerDebitAuctionUpdated(Balance),
/// The fixed size for debit auction updated. [new_size]
DebitAuctionFixedSizeUpdated(Balance),
/// The fixed size for collateral auction under specific collateral type updated. [collateral_type, new_size]
CollateralAuctionMaximumSizeUpdated(CurrencyId, Balance),
}
);
decl_error! {
/// Error for cdp treasury module.
pub enum Error for Module<T: Trait> {
/// The collateral amount of CDP treasury is not enough
CollateralNotEnough,
/// Collateral Amount overflow
CollateralOverflow,
/// Surplus pool overflow
SurplusPoolOverflow,
/// debit pool overflow
DebitPoolOverflow,
}
}
decl_storage! {
trait Store for Module<T: Trait> as CDPTreasury {
/// The fixed amount of stable coin for sale per surplus auction
pub SurplusAuctionFixedSize get(fn surplus_auction_fixed_size) config(): Balance;
/// The buffer size of surplus pool, the system will process the surplus through
/// surplus auction when above this value
pub SurplusBufferSize get(fn surplus_buffer_size) config(): Balance;
/// Initial amount of native token for sale per debit auction
pub InitialAmountPerDebitAuction get(fn initial_amount_per_debit_auction) config(): Balance;
/// The fixed amount of stable coin per surplus auction wants to get
pub DebitAuctionFixedSize get(fn debit_auction_fixed_size) config(): Balance;
/// The maximum amount of collateral amount for sale per collateral auction
pub CollateralAuctionMaximumSize get(fn collateral_auction_maximum_size): map hasher(twox_64_concat) CurrencyId => Balance;
/// Current total debit value of system. It's not same as debit in CDP engine,
/// it is the bad debt of the system.
pub DebitPool get(fn debit_pool): Balance;
/// Current total surplus of system.
pub SurplusPool get(fn surplus_pool): Balance;
/// Mapping from collateral type to collateral assets amount kept in CDP treasury
pub TotalCollaterals get(fn total_collaterals): map hasher(twox_64_concat) CurrencyId => Balance;
/// System shutdown flag
pub IsShutdown get(fn is_shutdown): bool;
}
add_extra_genesis {
config(collateral_auction_maximum_size): Vec<(CurrencyId, Balance)>;
build(|config: &GenesisConfig| {
config.collateral_auction_maximum_size.iter().for_each(|(currency_id, size)| {
CollateralAuctionMaximumSize::insert(currency_id, size);
})
})
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
/// Stablecoin currency id
const GetStableCurrencyId: CurrencyId = T::GetStableCurrencyId::get();
/// Lots cap when create auction
const MaxAuctionsCount: u32 = T::MaxAuctionsCount::get();
/// The CDP treasury's module id, keep surplus and collateral assets from liquidation.
const ModuleId: ModuleId = T::ModuleId::get();
/// Update parameters related to surplus and debit auction
///
/// The dispatch origin of this call must be `UpdateOrigin`.
///
/// - `surplus_auction_fixed_size`: new fixed amount of stable coin for sale per surplus auction, `None` means do not update
/// - `surplus_buffer_size`: new buffer size of surplus pool, `None` means do not update
/// - `initial_amount_per_debit_auction`: initial amount of native token for sale per debit auction, `None` means do not update
/// - `debit_auction_fixed_size`: the fixed amount of stable coin per collateral auction wants to get, `None` means do not update
///
/// # <weight>
/// - Complexity: `O(1)`
/// - Db reads:
/// - Db writes: `SurplusAuctionFixedSize`, `SurplusBufferSize`, `InitialAmountPerDebitAuction`, `DebitAuctionFixedSize`
/// -------------------
/// Base Weight: 20.18 µs
/// # </weight>
#[weight = (20 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(0, 4), DispatchClass::Operational)]
pub fn set_debit_and_surplus_handle_params(
origin,
surplus_auction_fixed_size: Option<Balance>,
surplus_buffer_size: Option<Balance>,
initial_amount_per_debit_auction: Option<Balance>,
debit_auction_fixed_size: Option<Balance>,
) {
with_transaction_result(|| {
T::UpdateOrigin::ensure_origin(origin)?;
if let Some(amount) = surplus_auction_fixed_size {
SurplusAuctionFixedSize::put(amount);
Self::deposit_event(Event::SurplusAuctionFixedSizeUpdated(amount));
}
if let Some(amount) = surplus_buffer_size {
SurplusBufferSize::put(amount);
Self::deposit_event(Event::SurplusBufferSizeUpdated(amount));
}
if let Some(amount) = initial_amount_per_debit_auction {
InitialAmountPerDebitAuction::put(amount);
Self::deposit_event(Event::InitialAmountPerDebitAuctionUpdated(amount));
}
if let Some(amount) = debit_auction_fixed_size {
DebitAuctionFixedSize::put(amount);
Self::deposit_event(Event::DebitAuctionFixedSizeUpdated(amount));
}
Ok(())
})?;
}
/// Update parameters related to collateral auction under specific collateral type
///
/// The dispatch origin of this call must be `UpdateOrigin`.
///
/// - `currency_id`: collateral type
/// - `surplus_buffer_size`: collateral auction maximum size
///
/// # <weight>
/// - Complexity: `O(1)`
/// - Db reads:
/// - Db writes: `CollateralAuctionMaximumSize`
/// -------------------
/// Base Weight: 15.59 µs
/// # </weight>
#[weight = (16 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(0, 1), DispatchClass::Operational)]
pub fn set_collateral_auction_maximum_size(origin, currency_id: CurrencyId, size: Balance) {
with_transaction_result(|| {
T::UpdateOrigin::ensure_origin(origin)?;
CollateralAuctionMaximumSize::insert(currency_id, size);
Self::deposit_event(Event::CollateralAuctionMaximumSizeUpdated(currency_id, size));
Ok(())
})?;
}
/// Handle excessive surplus or debits of system when block end
fn on_finalize(_now: T::BlockNumber) {
// offset the same amount between debit pool and surplus pool
Self::offset_surplus_and_debit();
// Stop to create surplus auction and debit auction after emergency shutdown happend.
if !Self::is_shutdown() {
let max_auctions_count: u32 = T::MaxAuctionsCount::get();
let mut created_lots: u32 = 0;
let surplus_auction_fixed_size = Self::surplus_auction_fixed_size();
if !surplus_auction_fixed_size.is_zero() {
let mut remain_surplus_pool = Self::surplus_pool();
let surplus_buffer_size = Self::surplus_buffer_size();
let total_surplus_in_auction = T::AuctionManagerHandler::get_total_surplus_in_auction();
// create surplus auction requires:
// surplus_pool >= total_surplus_in_auction + surplus_buffer_size + surplus_auction_fixed_size
while remain_surplus_pool >= total_surplus_in_auction + surplus_buffer_size + surplus_auction_fixed_size {
if max_auctions_count != 0 && created_lots >= max_auctions_count {
break
}
T::AuctionManagerHandler::new_surplus_auction(surplus_auction_fixed_size);
created_lots += 1;
remain_surplus_pool -= surplus_auction_fixed_size;
}
}
let debit_auction_fixed_size = Self::debit_auction_fixed_size();
let initial_amount_per_debit_auction = Self::initial_amount_per_debit_auction();
if !debit_auction_fixed_size.is_zero() && !initial_amount_per_debit_auction.is_zero() {
let mut remain_debit_pool = Self::debit_pool();
let total_debit_in_auction = T::AuctionManagerHandler::get_total_debit_in_auction();
let total_target_in_auction = T::AuctionManagerHandler::get_total_target_in_auction();
// create debit auction requires:
// debit_pool > total_debit_in_auction + total_target_in_auction + debit_auction_fixed_size
while remain_debit_pool >= total_debit_in_auction + total_target_in_auction + debit_auction_fixed_size {
if max_auctions_count != 0 && created_lots >= max_auctions_count {
break
}
T::AuctionManagerHandler::new_debit_auction(initial_amount_per_debit_auction, debit_auction_fixed_size);
created_lots += 1;
remain_debit_pool -= debit_auction_fixed_size;
}
}
}
}
}
}
impl<T: Trait> Module<T> {
pub fn account_id() -> T::AccountId {
T::ModuleId::get().into_account()
}
fn offset_surplus_and_debit() {
let offset_amount = sp_std::cmp::min(Self::debit_pool(), Self::surplus_pool());
// burn the amount that is equal to offset amount of stable coin.
if !offset_amount.is_zero()
&& T::Currency::withdraw(T::GetStableCurrencyId::get(), &Self::account_id(), offset_amount).is_ok()
{
DebitPool::mutate(|debit| *debit -= offset_amount);
SurplusPool::mutate(|surplus| *surplus -= offset_amount);
}
}
}
impl<T: Trait> CDPTreasury<T::AccountId> for Module<T> {
type Balance = Balance;
type CurrencyId = CurrencyId;
fn get_surplus_pool() -> Self::Balance {
Self::surplus_pool()
}
fn get_debit_pool() -> Self::Balance {
Self::debit_pool()
}
fn get_total_collaterals(id: Self::CurrencyId) -> Self::Balance {
Self::total_collaterals(id)
}
fn get_debit_proportion(amount: Self::Balance) -> Ratio {
let stable_total_supply = T::Currency::total_issuance(T::GetStableCurrencyId::get());
Ratio::checked_from_rational(amount, stable_total_supply).unwrap_or_default()
}
fn on_system_debit(amount: Self::Balance) -> DispatchResult {
let new_debit_pool = Self::debit_pool()
.checked_add(amount)
.ok_or(Error::<T>::DebitPoolOverflow)?;
DebitPool::put(new_debit_pool);
Ok(())
}
fn on_system_surplus(amount: Self::Balance) -> DispatchResult {
let new_surplus_pool = Self::surplus_pool()
.checked_add(amount)
.ok_or(Error::<T>::SurplusPoolOverflow)?;
T::Currency::deposit(T::GetStableCurrencyId::get(), &Self::account_id(), amount)?;
SurplusPool::put(new_surplus_pool);
Ok(())
}
fn issue_debit(who: &T::AccountId, debit: Self::Balance, backed: bool) -> DispatchResult {
// increase the debit of same amount to cdp treasury for debit without any assets backed
if !backed {
let new_debit_pool = Self::debit_pool()
.checked_add(debit)
.ok_or(Error::<T>::DebitPoolOverflow)?;
T::Currency::deposit(T::GetStableCurrencyId::get(), who, debit)?;
DebitPool::put(new_debit_pool);
} else {
T::Currency::deposit(T::GetStableCurrencyId::get(), who, debit)?;
}
Ok(())
}
fn burn_debit(who: &T::AccountId, debit: Self::Balance) -> DispatchResult {
T::Currency::withdraw(T::GetStableCurrencyId::get(), who, debit)
}
fn deposit_surplus(from: &T::AccountId, surplus: Self::Balance) -> DispatchResult {
let new_surplus_pool = Self::surplus_pool()
.checked_add(surplus)
.ok_or(Error::<T>::SurplusPoolOverflow)?;
T::Currency::transfer(T::GetStableCurrencyId::get(), from, &Self::account_id(), surplus)?;
SurplusPool::put(new_surplus_pool);
Ok(())
}
fn deposit_collateral(from: &T::AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult {
let new_total_collateral = Self::total_collaterals(currency_id)
.checked_add(amount)
.ok_or(Error::<T>::CollateralOverflow)?;
T::Currency::transfer(currency_id, from, &Self::account_id(), amount)?;
TotalCollaterals::insert(currency_id, new_total_collateral);
Ok(())
}
fn withdraw_collateral(to: &T::AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult {
let new_total_collateral = Self::total_collaterals(currency_id)
.checked_sub(amount)
.ok_or(Error::<T>::CollateralNotEnough)?;
T::Currency::transfer(currency_id, &Self::account_id(), to, amount)?;
TotalCollaterals::insert(currency_id, new_total_collateral);
Ok(())
}
}
impl<T: Trait> CDPTreasuryExtended<T::AccountId> for Module<T> {
fn swap_collateral_to_stable(
currency_id: CurrencyId,
supply_amount: Balance,
target_amount: Balance,
) -> sp_std::result::Result<Balance, DispatchError> {
ensure!(
Self::total_collaterals(currency_id) >= supply_amount,
Error::<T>::CollateralNotEnough,
);
T::Currency::ensure_can_withdraw(currency_id, &Self::account_id(), supply_amount)?;
let amount = T::DEX::exchange_currency(
Self::account_id(),
currency_id,
supply_amount,
T::GetStableCurrencyId::get(),
target_amount,
)?;
TotalCollaterals::mutate(currency_id, |balance| *balance -= supply_amount);
SurplusPool::mutate(|surplus| *surplus += amount);
Ok(amount)
}
fn create_collateral_auctions(
currency_id: CurrencyId,
amount: Balance,
target: Balance,
refund_receiver: T::AccountId,
) {
if Self::total_collaterals(currency_id)
>= amount + T::AuctionManagerHandler::get_total_collateral_in_auction(currency_id)
{
let collateral_auction_maximum_size = Self::collateral_auction_maximum_size(currency_id);
let mut unhandled_collateral_amount = amount;
let mut unhandled_target = target;
let max_auctions_count: u32 = T::MaxAuctionsCount::get();
let mut created_lots: u32 = 0;
while !unhandled_collateral_amount.is_zero() {
let (lot_collateral_amount, lot_target) = if unhandled_collateral_amount
> collateral_auction_maximum_size
&& !collateral_auction_maximum_size.is_zero()
{
if max_auctions_count != 0 && created_lots >= max_auctions_count {
(unhandled_collateral_amount, unhandled_target)
} else {
created_lots += 1;
let proportion =
Ratio::checked_from_rational(collateral_auction_maximum_size, amount).unwrap_or_default();
(collateral_auction_maximum_size, proportion.saturating_mul_int(target))
}
} else {
(unhandled_collateral_amount, unhandled_target)
};
T::AuctionManagerHandler::new_collateral_auction(
&refund_receiver,
currency_id,
lot_collateral_amount,
lot_target,
);
unhandled_collateral_amount -= lot_collateral_amount;
unhandled_target -= lot_target;
}
}
}
}
impl<T: Trait> OnEmergencyShutdown for Module<T> {
fn on_emergency_shutdown() {
IsShutdown::put(true);
}
}