diff --git a/blockchain/modules/asset-registry/src/weights.rs b/blockchain/modules/asset-registry/src/weights.rs index db3f04be..db889bdb 100644 --- a/blockchain/modules/asset-registry/src/weights.rs +++ b/blockchain/modules/asset-registry/src/weights.rs @@ -50,8 +50,6 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn register_foreign_asset() -> Weight; fn update_foreign_asset() -> Weight; - fn register_stable_asset() -> Weight; - fn update_stable_asset() -> Weight; fn register_erc20_asset() -> Weight; fn update_erc20_asset() -> Weight; fn register_native_asset() -> Weight; @@ -77,19 +75,6 @@ impl WeightInfo for SetheumWeight { .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: AssetRegistry NextStableAssetId (r:1 w:1) - // Storage: AssetRegistry AssetMetadatas (r:1 w:1) - fn register_stable_asset() -> Weight { - Weight::from_parts(15_830_000, 0) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: AssetRegistry AssetMetadatas (r:1 w:1) - fn update_stable_asset() -> Weight { - Weight::from_parts(14_342_000, 0) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } // Storage: EVM Accounts (r:2 w:0) // Storage: EVM Codes (r:1 w:0) // Storage: EVM AccountStorages (r:5 w:0) @@ -132,16 +117,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - fn register_stable_asset() -> Weight { - Weight::from_parts(15_830_000, 0) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - fn update_stable_asset() -> Weight { - Weight::from_parts(14_342_000, 0) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } fn register_erc20_asset() -> Weight { Weight::from_parts(187_828_000, 0) .saturating_add(RocksDbWeight::get().reads(10 as u64)) diff --git a/blockchain/modules/ecdp-ussd-treasury/Cargo.toml b/blockchain/modules/ecdp-ussd-treasury/Cargo.toml index 82caa87a..4e2afaac 100644 --- a/blockchain/modules/ecdp-ussd-treasury/Cargo.toml +++ b/blockchain/modules/ecdp-ussd-treasury/Cargo.toml @@ -5,46 +5,40 @@ authors = ["Setheum Labs"] edition = "2021" [dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true } scale-info = { workspace = true } -serde = { workspace = true, optional = true } -parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } sp-runtime = { workspace = true } -sp-io = { workspace = true } -sp-std = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } +sp-std = { workspace = true } +orml-traits = { workspace = true } +module-support ={ workspace = true } +primitives = { workspace = true } [dev-dependencies] sp-core = { workspace = true, features = ["std"] } -pallet-balances = { workspace = true } -orml-tokens = { workspace = true } +sp-io = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +orml-currencies = { workspace = true, features = ["std"] } +orml-auction = { workspace = true, features = ["std"] } +module-dex = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } [features] default = ["std"] std = [ - "scale-info/std", - "serde", "parity-scale-codec/std", - "sp-runtime/std", - "sp-std/std", - "sp-io/std", "frame-support/std", "frame-system/std", - "primitives/std", - "support/std", "orml-traits/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "primitives/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/blockchain/modules/ecdp-ussd-treasury/src/lib.rs b/blockchain/modules/ecdp-ussd-treasury/src/lib.rs new file mode 100644 index 00000000..4571c1bb --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/src/lib.rs @@ -0,0 +1,539 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 . + +//! # ECDP USSD Treasury Module +//! +//! ## Overview +//! +//! The ECDP USSD Treasury manages the accumulated interest and bad debts generated by ECDPs, +//! 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 USSD. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] +#![allow(clippy::needless_range_loop)] + +use frame_support::{pallet_prelude::*, transactional, PalletId}; +use frame_system::pallet_prelude::*; +use module_support::{AuctionsManager, SlickUsdEcdpTreasury, SlickUsdEcdpTreasuryExtended, SwapManager, Ratio, Swap, SwapLimit}; +use orml_traits::{MultiCurrency, MultiCurrencyExtended}; +use primitives::{Balance, CurrencyId}; +use sp_runtime::{ + traits::{AccountIdConversion, One, Zero}, + ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, +}; +use sp_std::prelude::*; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The origin which may update parameters and handle + /// surplus/collateral. + type UpdateOrigin: EnsureOrigin; + + /// The Currency for managing assets related to ECDP + type Currency: MultiCurrencyExtended; + + /// Stablecoin currency id + #[pallet::constant] + type GetUSSDCurrencyId: Get; + + /// Auction manager creates auction to handle system surplus and debit + type AuctionsManagerHandler: AuctionsManager; + + /// Dex manager + type DEX: SwapManager; + + /// Swap + type Swap: Swap; + + /// 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. + #[pallet::constant] + type MaxAuctionsCount: Get; + + #[pallet::constant] + type TreasuryAccount: Get; + + /// The ECDP USSD Treasury's module id, stores the surplus and collateral assets. + #[pallet::constant] + type PalletId: Get; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The collateral amount of ECDP USSD Treasury is not enough + CollateralNotEnough, + /// The surplus pool of ECDP USSD Treasury is not enough + SurplusPoolNotEnough, + /// The debit pool of ECDP USSD Treasury is not enough + DebitPoolNotEnough, + /// Cannot use collateral to swap USSD + CannotSwap, + /// The currency id is not DexShare type + NotDexShare, + } + + #[pallet::event] + #[pallet::generate_deposit(pub fn deposit_event)] + pub enum Event { + /// The expected amount size for per lot collateral auction of specific collateral type + /// updated. + ExpectedCollateralAuctionSizeUpdated { + collateral_type: CurrencyId, + new_size: Balance, + }, + /// The buffer amount of debit pool that will not be offset by suplus pool updated. + DebitOffsetBufferUpdated { amount: Balance }, + } + + /// The expected amount size for per lot collateral auction of specific + /// collateral type. + /// + /// ExpectedCollateralAuctionSize: map CurrencyId => Balance + #[pallet::storage] + #[pallet::getter(fn expected_collateral_auction_size)] + pub type ExpectedCollateralAuctionSize = StorageMap<_, Twox64Concat, CurrencyId, Balance, ValueQuery>; + + /// Current total debit value of system. It's not same as debit in ECDP Egine, + /// it is the bad debt of the system. + /// + /// DebitPool: Balance + #[pallet::storage] + #[pallet::getter(fn debit_pool)] + pub type DebitPool = StorageValue<_, Balance, ValueQuery>; + + /// The buffer amount of debit pool that will not be offset by surplus pool. + /// + /// DebitOffsetBuffer: Balance + #[pallet::storage] + #[pallet::getter(fn debit_offset_buffer)] + pub type DebitOffsetBuffer = StorageValue<_, Balance, ValueQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub expected_collateral_auction_size: Vec<(CurrencyId, Balance)>, + pub _phantom: sp_std::marker::PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + self.expected_collateral_auction_size + .iter() + .for_each(|(currency_id, size)| { + ExpectedCollateralAuctionSize::::insert(currency_id, size); + }); + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet { + /// Handle excessive surplus or debits of system when block end + fn on_finalize(_now: BlockNumberFor) { + // offset the same amount between debit pool and surplus pool + Self::offset_surplus_and_debit(); + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::extract_surplus_to_treasury())] + pub fn extract_surplus_to_treasury(origin: OriginFor, #[pallet::compact] amount: Balance) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + T::Currency::transfer( + T::GetUSSDCurrencyId::get(), + &Self::account_id(), + &T::TreasuryAccount::get(), + amount, + )?; + Ok(()) + } + + /// Auction the collateral not occupied by the auction. + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `currency_id`: collateral type + /// - `amount`: collateral amount + /// - `target`: target amount + /// - `splited`: split collateral to multiple auction according to the config size + #[pallet::call_index(1)] + #[pallet::weight( + if *splited { + T::WeightInfo::auction_collateral(T::MaxAuctionsCount::get()) + } else { + T::WeightInfo::auction_collateral(1) + } + )] + pub fn auction_collateral( + origin: OriginFor, + currency_id: CurrencyId, + #[pallet::compact] amount: Balance, + #[pallet::compact] target: Balance, + splited: bool, + ) -> DispatchResultWithPostInfo { + T::UpdateOrigin::ensure_origin(origin)?; + let created_auctions = >::create_collateral_auctions( + currency_id, + amount, + target, + Self::account_id(), + splited, + )?; + Ok(Some(T::WeightInfo::auction_collateral(created_auctions)).into()) + } + + /// Swap the collateral not occupied by the auction to USSD. + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `currency_id`: collateral type + /// - `swap_limit`: target amount + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::exchange_collateral_to_ussd())] + pub fn exchange_collateral_to_ussd( + origin: OriginFor, + currency_id: CurrencyId, + swap_limit: SwapLimit, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + // the supply collateral must not be occupied by the auction. + Self::swap_collateral_to_ussd(currency_id, swap_limit, false)?; + Ok(()) + } + + /// Update parameters related to collateral auction under specific + /// collateral type + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `currency_id`: collateral type + /// - `amount`: expected size of per lot collateral auction + #[pallet::call_index(3)] + #[pallet::weight((T::WeightInfo::set_expected_collateral_auction_size(), DispatchClass::Operational))] + pub fn set_expected_collateral_auction_size( + origin: OriginFor, + currency_id: CurrencyId, + #[pallet::compact] size: Balance, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + ExpectedCollateralAuctionSize::::insert(currency_id, size); + Self::deposit_event(Event::ExpectedCollateralAuctionSizeUpdated { + collateral_type: currency_id, + new_size: size, + }); + Ok(()) + } + + /// Update the debit offset buffer + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `amount`: the buffer amount of debit pool + #[pallet::call_index(4)] + #[pallet::weight((T::WeightInfo::set_expected_collateral_auction_size(), DispatchClass::Operational))] + pub fn set_debit_offset_buffer(origin: OriginFor, #[pallet::compact] amount: Balance) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + DebitOffsetBuffer::::mutate(|v| { + if *v != amount { + *v = amount; + Self::deposit_event(Event::DebitOffsetBufferUpdated { amount }); + } + }); + Ok(()) + } + } +} + +impl Pallet { + /// Get account of cdp treasury module. + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + /// Get current total surplus of system. + pub fn surplus_pool() -> Balance { + T::Currency::free_balance(T::GetUSSDCurrencyId::get(), &Self::account_id()) + } + + /// Get total collateral amount of cdp treasury module. + pub fn total_collaterals(currency_id: CurrencyId) -> Balance { + T::Currency::free_balance(currency_id, &Self::account_id()) + } + + /// Get collateral amount not in auction + pub fn total_collaterals_not_in_auction(currency_id: CurrencyId) -> Balance { + T::Currency::free_balance(currency_id, &Self::account_id()) + .saturating_sub(T::AuctionsManagerHandler::get_total_collateral_in_auction(currency_id)) + } + + fn offset_surplus_and_debit() { + // The part of the debit pool that exceeds the debit offset buffer can be offset by the surplus + let offset_amount = sp_std::cmp::min( + Self::debit_pool().saturating_sub(Self::debit_offset_buffer()), + Self::surplus_pool(), + ); + + // Burn the amount that is equal to offset amount of USSD + if !offset_amount.is_zero() { + let res = Self::burn_debit(&Self::account_id(), offset_amount); + match res { + Ok(_) => { + DebitPool::::mutate(|debit| { + *debit = debit + .checked_sub(offset_amount) + .expect("offset = min(debit, surplus); qed") + }); + } + Err(e) => { + log::warn!( + target: "cdp-treasury", + "get_swap_supply_amount: Attempt to burn surplus {:?} failed: {:?}, this is unexpected but should be safe", + offset_amount, e + ); + } + } + } + } +} + +impl SlickUsdEcdpTreasury for Pallet { + 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 ussd_total_supply = T::Currency::total_issuance(T::GetUSSDCurrencyId::get()); + Ratio::checked_from_rational(amount, ussd_total_supply).unwrap_or_default() + } + + fn on_system_debit(amount: Self::Balance) -> DispatchResult { + DebitPool::::try_mutate(|debit_pool| -> DispatchResult { + *debit_pool = debit_pool.checked_add(amount).ok_or(ArithmeticError::Overflow)?; + Ok(()) + }) + } + + fn on_system_surplus(amount: Self::Balance) -> DispatchResult { + Self::issue_debit(&Self::account_id(), amount, true) + } + + /// This should be the only function in the system that issues USSD + fn issue_debit(who: &T::AccountId, debit: Self::Balance, backed: bool) -> DispatchResult { + // increase system debit if the debit is unbacked + if !backed { + Self::on_system_debit(debit)?; + } + T::Currency::deposit(T::GetUSSDCurrencyId::get(), who, debit)?; + + Ok(()) + } + + /// This should be the only function in the system that burns USSD + fn burn_debit(who: &T::AccountId, debit: Self::Balance) -> DispatchResult { + T::Currency::withdraw(T::GetUSSDCurrencyId::get(), who, debit) + } + + fn deposit_surplus(from: &T::AccountId, surplus: Self::Balance) -> DispatchResult { + T::Currency::transfer(T::GetUSSDCurrencyId::get(), from, &Self::account_id(), surplus) + } + + fn withdraw_surplus(to: &T::AccountId, surplus: Self::Balance) -> DispatchResult { + T::Currency::transfer(T::GetUSSDCurrencyId::get(), &Self::account_id(), to, surplus) + } + + fn deposit_collateral(from: &T::AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult { + T::Currency::transfer(currency_id, from, &Self::account_id(), amount) + } + + fn withdraw_collateral(to: &T::AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult { + T::Currency::transfer(currency_id, &Self::account_id(), to, amount) + } +} + +impl SlickUsdEcdpTreasuryExtended for Pallet { + #[transactional] + fn swap_collateral_to_ussd( + currency_id: CurrencyId, + limit: SwapLimit, + collateral_in_auction: bool, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let supply_limit = match limit { + SwapLimit::ExactSupply(supply_amount, _) => supply_amount, + SwapLimit::ExactTarget(max_supply_amount, _) => max_supply_amount, + }; + let target_limit = match limit { + SwapLimit::ExactSupply(_, minimum_target_amount) => minimum_target_amount, + SwapLimit::ExactTarget(_, exact_target_amount) => exact_target_amount, + }; + + if collateral_in_auction { + ensure!( + Self::total_collaterals(currency_id) >= supply_limit + && T::AuctionsManagerHandler::get_total_collateral_in_auction(currency_id) >= supply_limit, + Error::::CollateralNotEnough, + ); + } else { + ensure!( + Self::total_collaterals_not_in_auction(currency_id) >= supply_limit, + Error::::CollateralNotEnough, + ); + } + + T::Swap::swap(&Self::account_id(), currency_id, T::GetUSSDCurrencyId::get(), limit)?; + } + + fn create_collateral_auctions( + currency_id: CurrencyId, + amount: Balance, + target: Balance, + refund_receiver: T::AccountId, + splited: bool, + ) -> Result { + ensure!( + Self::total_collaterals_not_in_auction(currency_id) >= amount, + Error::::CollateralNotEnough, + ); + + let mut unhandled_collateral_amount = amount; + let mut unhandled_target = target; + let expected_collateral_auction_size = Self::expected_collateral_auction_size(currency_id); + let max_auctions_count: Balance = T::MaxAuctionsCount::get().into(); + let lots_count = if !splited + || max_auctions_count.is_zero() + || expected_collateral_auction_size.is_zero() + || amount <= expected_collateral_auction_size + { + One::one() + } else { + let mut count = amount + .checked_div(expected_collateral_auction_size) + .expect("collateral auction maximum size is not zero; qed"); + + let remainder = amount + .checked_rem(expected_collateral_auction_size) + .expect("collateral auction maximum size is not zero; qed"); + if !remainder.is_zero() { + count = count.saturating_add(One::one()); + } + sp_std::cmp::min(count, max_auctions_count) + }; + let average_amount_per_lot = amount.checked_div(lots_count).expect("lots count is at least 1; qed"); + let average_target_per_lot = target.checked_div(lots_count).expect("lots count is at least 1; qed"); + let mut created_lots: Balance = Zero::zero(); + + while !unhandled_collateral_amount.is_zero() { + created_lots = created_lots.saturating_add(One::one()); + let (lot_collateral_amount, lot_target) = if created_lots == lots_count { + // the last lot may be have some remnant than average + (unhandled_collateral_amount, unhandled_target) + } else { + (average_amount_per_lot, average_target_per_lot) + }; + + T::AuctionsManagerHandler::new_collateral_auction( + &refund_receiver, + currency_id, + lot_collateral_amount, + lot_target, + )?; + + unhandled_collateral_amount = unhandled_collateral_amount.saturating_sub(lot_collateral_amount); + unhandled_target = unhandled_target.saturating_sub(lot_target); + } + let created_auctions: u32 = created_lots.try_into().map_err(|_| ArithmeticError::Overflow)?; + Ok(created_auctions) + } + + fn remove_liquidity_for_lp_collateral( + lp_currency_id: CurrencyId, + amount: Balance, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let (currency_id_0, currency_id_1) = lp_currency_id + .split_dex_share_currency_id() + .ok_or(Error::::NotDexShare)?; + T::DEX::remove_liquidity( + &Self::account_id(), + currency_id_0, + currency_id_1, + amount, + Zero::zero(), + Zero::zero(), + false, + ) + } + + fn max_auction() -> u32 { + T::MaxAuctionsCount::get() + } +} + +pub struct InitializeDebitOffsetBuffer( + sp_std::marker::PhantomData, + sp_std::marker::PhantomData, +); +impl> frame_support::traits::OnRuntimeUpgrade + for InitializeDebitOffsetBuffer +{ + fn on_runtime_upgrade() -> Weight { + let amount = GetBufferSize::get(); + DebitOffsetBuffer::::mutate(|v| { + if *v != amount { + *v = amount; + Pallet::::deposit_event(Event::DebitOffsetBufferUpdated { amount }); + } + }); + + Weight::from_parts(0, 0) + } +} diff --git a/blockchain/modules/ecdp-ussd-treasury/src/mock.rs b/blockchain/modules/ecdp-ussd-treasury/src/mock.rs new file mode 100644 index 00000000..e365ba3f --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/src/mock.rs @@ -0,0 +1,259 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 . + +//! Mocks for the cdp treasury module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, EitherOfDiverse, Nothing}, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use module_support::SpecificJointsSwap; +use orml_traits::parameter_type_with_key; +use primitives::{DexShare, TokenSymbol, TradingPair}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; +use sp_std::cell::RefCell; + +pub type AccountId = u128; +pub type BlockNumber = u64; +pub type Amount = i64; +pub type AuctionId = u32; + +pub const ALICE: AccountId = 0; +pub const BOB: AccountId = 1; +pub const CHARLIE: AccountId = 2; +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const BTC: CurrencyId = CurrencyId::ForeignAsset(255); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); +pub const LP_USSD_EDF: CurrencyId = + CurrencyId::DexShare(DexShare::Token(TokenSymbol::USSD), DexShare::Token(TokenSymbol::EDF)); + +mod cdp_treasury { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = frame_system::Pallet; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = SEE; +} + +impl orml_currencies::Config for Runtime { + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} + +parameter_types! { + pub const GetUSSDCurrencyId: CurrencyId = USSD; + pub const GetExchangeFee: (u32, u32) = (0, 100); + pub EnabledTradingPairs: Vec = vec![ + TradingPair::from_currency_ids(USSD, BTC).unwrap(), + TradingPair::from_currency_ids(USSD, EDF).unwrap(), + TradingPair::from_currency_ids(BTC, EDF).unwrap(), + ]; + pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); +} + +impl module_dex::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = ConstU32<4>; + type PalletId = DEXPalletId; + type Erc20InfoMapping = (); + type DEXIncentives = (); + type WeightInfo = (); + type ListingOrigin = EnsureSignedBy; + type ExtendedProvisioningBlocks = ConstU64<0>; + type OnLiquidityPoolUpdated = (); +} + +thread_local! { + pub static TOTAL_COLLATERAL_AUCTION: RefCell = RefCell::new(0); + pub static TOTAL_COLLATERAL_IN_AUCTION: RefCell = RefCell::new(0); +} + +pub struct MockAuctionsManager; +impl AuctionsManager for MockAuctionsManager { + type CurrencyId = CurrencyId; + type Balance = Balance; + type AuctionId = AuctionId; + + fn new_collateral_auction( + _refund_recipient: &AccountId, + _currency_id: Self::CurrencyId, + amount: Self::Balance, + _target: Self::Balance, + ) -> DispatchResult { + TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut() += 1); + TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut() += amount); + Ok(()) + } + + fn cancel_auction(_id: Self::AuctionId) -> DispatchResult { + unimplemented!() + } + + fn get_total_collateral_in_auction(_id: Self::CurrencyId) -> Self::Balance { + TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()) + } + + fn get_total_target_in_auction() -> Self::Balance { + unimplemented!() + } +} + +ord_parameter_types! { + pub const One: AccountId = 1; +} + +parameter_types! { + pub const SlickUsdEcdpTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); + pub const TreasuryAccount: AccountId = 10; + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![EDF], + ]; +} + +thread_local! { + static IS_SHUTDOWN: RefCell = RefCell::new(false); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type AuctionsManagerHandler = MockAuctionsManager; + type UpdateOrigin = EitherOfDiverse, EnsureSignedBy>; + type DEX = DEXModule; + type Swap = SpecificJointsSwap; + type MaxAuctionsCount = ConstU32<5>; + type PalletId = SlickUsdEcdpTreasuryPalletId; + type TreasuryAccount = TreasuryAccount; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + SlickUsdEcdpTreasuryModule: cdp_treasury, + Currencies: orml_currencies, + Tokens: orml_tokens, + PalletBalances: pallet_balances, + DEXModule: module_dex, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![ + (ALICE, EDF, 1000), + (ALICE, USSD, 1000), + (ALICE, BTC, 1000), + (BOB, EDF, 1000), + (BOB, USSD, 1000), + (BOB, BTC, 1000), + (CHARLIE, EDF, 1000), + (CHARLIE, BTC, 1000), + ], + } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .unwrap(); + + module_dex::GenesisConfig:: { + initial_listing_trading_pairs: vec![], + initial_enabled_trading_pairs: EnabledTradingPairs::get(), + initial_added_liquidity_pools: vec![], + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/blockchain/modules/ecdp-ussd-treasury/src/tests.rs b/blockchain/modules/ecdp-ussd-treasury/src/tests.rs new file mode 100644 index 00000000..8f70d9bc --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/src/tests.rs @@ -0,0 +1,571 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 . + +//! Unit tests for the cdp treasury module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{RuntimeEvent, *}; +use module_support::SwapError; +use sp_runtime::traits::BadOrigin; + +#[test] +fn surplus_pool_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_ok!(Currencies::deposit( + GetUSSDCurrencyId::get(), + &SlickUsdEcdpTreasuryModule::account_id(), + 500 + )); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 500); + }); +} + +#[test] +fn total_collaterals_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 0); + assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10); + }); +} + +#[test] +fn on_system_debit_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(1000)); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); + assert_noop!( + SlickUsdEcdpTreasuryModule::on_system_debit(Balance::max_value()), + ArithmeticError::Overflow, + ); + }); +} + +#[test] +fn on_system_surplus_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + }); +} + +#[test] +fn offset_surplus_and_debit_on_finalize_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + SlickUsdEcdpTreasuryModule::on_finalize(1); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(300)); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 300); + SlickUsdEcdpTreasuryModule::on_finalize(2); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 700); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 700); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(800)); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 800); + SlickUsdEcdpTreasuryModule::on_finalize(3); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 100); + }); +} + +#[test] +fn issue_debit_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + + assert_ok!(SlickUsdEcdpTreasuryModule::issue_debit(&ALICE, 1000, true)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 2000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + + assert_ok!(SlickUsdEcdpTreasuryModule::issue_debit(&ALICE, 1000, false)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 3000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); + }); +} + +#[test] +fn burn_debit_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::burn_debit(&ALICE, 300)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + }); +} + +#[test] +fn deposit_surplus_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_surplus(&ALICE, 300)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 300); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 300); + }); +} + +#[test] +fn withdraw_surplus_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_surplus(&ALICE, 300)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 300); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 300); + + assert_ok!(SlickUsdEcdpTreasuryModule::withdraw_surplus(&ALICE, 200)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 100); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 100); + }); +} + +#[test] +fn deposit_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 0); + assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); + assert!(!SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 10000).is_ok()); + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 500); + assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 500); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 500); + }); +} + +#[test] +fn withdraw_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 500); + assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 500); + assert_eq!(Currencies::free_balance(BTC, &BOB), 1000); + assert!(!SlickUsdEcdpTreasuryModule::withdraw_collateral(&BOB, BTC, 501).is_ok()); + assert_ok!(SlickUsdEcdpTreasuryModule::withdraw_collateral(&BOB, BTC, 400)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 100); + assert_eq!(Currencies::free_balance(BTC, &BOB), 1400); + }); +} + +#[test] +fn get_total_collaterals_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(SlickUsdEcdpTreasuryModule::get_total_collaterals(BTC), 500); + }); +} + +#[test] +fn get_debit_proportion_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!( + SlickUsdEcdpTreasuryModule::get_debit_proportion(100), + Ratio::saturating_from_rational(100, Currencies::total_issuance(USSD)) + ); + }); +} + +#[test] +fn swap_collateral_to_ussd_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&BOB, BTC, 200)); + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&CHARLIE, EDF, 1000)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 200); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(EDF), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_ok!(DEXModule::add_liquidity( + RuntimeOrigin::signed(BOB), + EDF, + USSD, + 1000, + 1000, + 0, + false + )); + + assert_noop!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(201, 200), false), + Error::::CollateralNotEnough, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1001, 0), false), + Error::::CollateralNotEnough, + ); + + assert_noop!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(200, 399), false), + SwapError::CannotSwap + ); + assert_ok!(DEXModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + BTC, + EDF, + 100, + 1000, + 0, + false + )); + + assert_eq!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(200, 399), false).unwrap(), + (198, 399) + ); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 399); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 2); + + assert_noop!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1000, 1000), false), + SwapError::CannotSwap + ); + + assert_eq!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1000, 0), false).unwrap(), + (1000, 225) + ); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 624); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(EDF), 0); + }); +} + +#[test] +fn create_collateral_auctions_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10000)); + assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_noop!( + SlickUsdEcdpTreasuryModule::create_collateral_auctions(BTC, 10001, 1000, ALICE, true), + Error::::CollateralNotEnough, + ); + + // without collateral auction maximum size + assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + BTC, 1000, 1000, ALICE, true + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 1); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 1000); + + // set collateral auction maximum size + assert_ok!(SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size( + RuntimeOrigin::signed(1), + BTC, + 300 + )); + + // amount < collateral auction maximum size + // auction + 1 + assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + BTC, 200, 1000, ALICE, true + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 2); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 1200); + + // not exceed lots count cap + // auction + 4 + assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + BTC, 1000, 1000, ALICE, true + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 6); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 2200); + + // exceed lots count cap + // auction + 5 + assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + BTC, 2000, 1000, ALICE, true + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 11); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 4200); + }); +} + +#[test] +fn remove_liquidity_for_lp_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(DEXModule::add_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 1000, + 100, + 0, + false + )); + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&BOB, LP_USSD_EDF, 200)); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 2000); + assert_eq!(DEXModule::get_liquidity_pool(USSD, EDF), (1000, 100)); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &SlickUsdEcdpTreasuryModule::account_id()), + 200 + ); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(EDF, &SlickUsdEcdpTreasuryModule::account_id()), 0); + + assert_noop!( + SlickUsdEcdpTreasuryModule::remove_liquidity_for_lp_collateral(EDF, 200), + Error::::NotDexShare + ); + + assert_eq!( + SlickUsdEcdpTreasuryModule::remove_liquidity_for_lp_collateral(LP_USSD_EDF, 120), + Ok((60, 6)) + ); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 1880); + assert_eq!(DEXModule::get_liquidity_pool(USSD, EDF), (940, 94)); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &SlickUsdEcdpTreasuryModule::account_id()), + 80 + ); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 60); + assert_eq!(Currencies::free_balance(EDF, &SlickUsdEcdpTreasuryModule::account_id()), 6); + }); +} + +#[test] +fn set_expected_collateral_auction_size_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_noop!( + SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size(RuntimeOrigin::signed(5), BTC, 200), + BadOrigin + ); + assert_ok!(SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size( + RuntimeOrigin::signed(1), + BTC, + 200 + )); + System::assert_last_event(RuntimeEvent::SlickUsdEcdpTreasuryModule( + crate::Event::ExpectedCollateralAuctionSizeUpdated { + collateral_type: BTC, + new_size: 200, + }, + )); + }); +} + +#[test] +fn extract_surplus_to_treasury_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_eq!(Currencies::free_balance(USSD, &TreasuryAccount::get()), 0); + + assert_noop!( + SlickUsdEcdpTreasuryModule::extract_surplus_to_treasury(RuntimeOrigin::signed(5), 200), + BadOrigin + ); + assert_ok!(SlickUsdEcdpTreasuryModule::extract_surplus_to_treasury( + RuntimeOrigin::signed(1), + 200 + )); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 800); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 800); + assert_eq!(Currencies::free_balance(USSD, &TreasuryAccount::get()), 200); + }); +} + +#[test] +fn auction_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10000)); + assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10000); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 10000); + assert_noop!( + SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(5), BTC, 10000, 1000, false), + BadOrigin, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(1), BTC, 10001, 1000, false), + Error::::CollateralNotEnough, + ); + + assert_ok!(SlickUsdEcdpTreasuryModule::auction_collateral( + RuntimeOrigin::signed(1), + BTC, + 1000, + 1000, + false + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 1); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 1000); + + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10000); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 9000); + assert_noop!( + SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(1), BTC, 9001, 1000, false), + Error::::CollateralNotEnough, + ); + }); +} + +#[test] +fn exchange_collateral_to_ussd_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(DEXModule::add_liquidity( + RuntimeOrigin::signed(BOB), + BTC, + USSD, + 200, + 1000, + 0, + false + )); + + assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 1000)); + assert_ok!(SlickUsdEcdpTreasuryModule::auction_collateral( + RuntimeOrigin::signed(1), + BTC, + 800, + 1000, + false + )); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 200); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + + assert_noop!( + SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(5), + BTC, + SwapLimit::ExactTarget(200, 200) + ), + BadOrigin, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(1), + BTC, + SwapLimit::ExactTarget(201, 200) + ), + Error::::CollateralNotEnough, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(1), + BTC, + SwapLimit::ExactSupply(201, 0) + ), + Error::::CollateralNotEnough, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(1), + BTC, + SwapLimit::ExactTarget(200, 1000) + ), + SwapError::CannotSwap + ); + + assert_ok!(SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(1), + BTC, + SwapLimit::ExactTarget(200, 399) + )); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 399); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 867); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 67); + }); +} + +#[test] +fn set_debit_offset_buffer_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + assert_noop!( + SlickUsdEcdpTreasuryModule::set_debit_offset_buffer(RuntimeOrigin::signed(5), 200), + BadOrigin + ); + assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + RuntimeOrigin::signed(1), + 200 + )); + System::assert_last_event(RuntimeEvent::SlickUsdEcdpTreasuryModule( + crate::Event::DebitOffsetBufferUpdated { amount: 200 }, + )); + }); +} + +#[test] +fn offset_surplus_and_debit_limited_by_debit_offset_buffer() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(2000)); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 2000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + + // offset all debit pool when surplus is enough + SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + + assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + RuntimeOrigin::signed(1), + 100 + )); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 100); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(2000)); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 2000); + + // keep the buffer for debit pool when surplus is enough + SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1100); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 100); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 100); + + assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + RuntimeOrigin::signed(1), + 200 + )); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 200); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(1400)); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1500); + + SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 400); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 200); + }); +} diff --git a/blockchain/modules/ecdp-ussd-treasury/src/weights.rs b/blockchain/modules/ecdp-ussd-treasury/src/weights.rs new file mode 100644 index 00000000..b1884981 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/src/weights.rs @@ -0,0 +1,108 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 . + +//! Autogenerated weights for module_ecdp_ussd_treasury +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-01-27, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_ecdp_ussd_treasury +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./modules/cdp-treasury/src/weights.rs +// --template=./templates/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_ecdp_ussd_treasury. +pub trait WeightInfo { + fn extract_surplus_to_treasury() -> Weight; + fn auction_collateral(b: u32) -> Weight; + fn exchange_collateral_to_ussd() -> Weight; + fn set_expected_collateral_auction_size() -> Weight; +} + +/// Weights for module_ecdp_ussd_treasury using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn auction_collateral(b: u32, ) -> Weight { + Weight::from_parts(2_672_000, 0) + // Standard Error: 326_000 + .saturating_add(Weight::from_parts(32_334_000, 0).saturating_mul(b as u64)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(b as u64))) + } + fn exchange_collateral_to_ussd() -> Weight { + Weight::from_parts(176_000_000, 0) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + fn set_expected_collateral_auction_size() -> Weight { + Weight::from_parts(25_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn extract_surplus_to_treasury() -> Weight { + Weight::from_parts(75_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn auction_collateral(b: u32, ) -> Weight { + Weight::from_parts(2_672_000, 0) + .saturating_add(Weight::from_parts(32_334_000, 0).saturating_mul(b as u64)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(b as u64))) + } + fn exchange_collateral_to_ussd() -> Weight { + Weight::from_parts(176_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + fn set_expected_collateral_auction_size() -> Weight { + Weight::from_parts(25_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn extract_surplus_to_treasury() -> Weight { + Weight::from_parts(75_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/blockchain/modules/edfis-swap/src/mock.rs b/blockchain/modules/edfis-swap/src/mock.rs index 1be114ba..eaf475ac 100644 --- a/blockchain/modules/edfis-swap/src/mock.rs +++ b/blockchain/modules/edfis-swap/src/mock.rs @@ -41,7 +41,8 @@ pub const ALICE: AccountId = 1; pub const BOB: AccountId = 2; pub const CAROL: AccountId = 3; pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); -pub const WBTC: CurrencyId = CurrencyId::Token(TokenSymbol::FA_WBTC); +pub const WBTC: CurrencyId = CurrencyId::ForeignAsset(255); + pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); diff --git a/blockchain/modules/evm/src/bench/mock.rs b/blockchain/modules/evm/src/bench/mock.rs index 3216c762..5ff7aa77 100644 --- a/blockchain/modules/evm/src/bench/mock.rs +++ b/blockchain/modules/evm/src/bench/mock.rs @@ -172,7 +172,6 @@ impl Config for Runtime { } parameter_types! { - pub const GetStableCurrencyId: CurrencyId = USSD; pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::one(); pub const TreasuryPalletId: PalletId = PalletId(*b"set/trsy"); pub const TransactionPaymentPalletId: PalletId = PalletId(*b"set/fees"); diff --git a/blockchain/modules/prices/README.md b/blockchain/modules/prices/README.md index 11f30725..1e34c5c1 100644 --- a/blockchain/modules/prices/README.md +++ b/blockchain/modules/prices/README.md @@ -5,6 +5,6 @@ ## Overview The data from Oracle cannot be used in business, prices module will do some process and feed prices for Setheum. Process include: - - specify a fixed price for stable currency; + - specify a fixed price for USSD; - feed price in USD or related price bewteen two currencies; - lock/unlock the price data got from oracle; diff --git a/blockchain/modules/prices/src/lib.rs b/blockchain/modules/prices/src/lib.rs index 1279f6a9..b9bce348 100644 --- a/blockchain/modules/prices/src/lib.rs +++ b/blockchain/modules/prices/src/lib.rs @@ -24,7 +24,7 @@ //! //! The data from Oracle cannot be used in business, prices module will do some //! process and feed prices for Setheum. Process include: -//! - specify a fixed price for stable currency +//! - specify a fixed price for USSD //! - feed price in USD or related price bewteen two currencies //! - lock/unlock the price data got from oracle diff --git a/blockchain/modules/prices/src/tests.rs b/blockchain/modules/prices/src/tests.rs index 42f66356..8d34cc24 100644 --- a/blockchain/modules/prices/src/tests.rs +++ b/blockchain/modules/prices/src/tests.rs @@ -119,7 +119,7 @@ fn lp_token_fair_price_works() { } #[test] -fn access_price_of_stable_currency() { +fn access_price_of_ussd() { ExtBuilder::default().build().execute_with(|| { assert_eq!( PricesModule::access_price(USSD), diff --git a/blockchain/modules/support/src/ecdp.rs b/blockchain/modules/support/src/ecdp.rs index 7967456e..a73c8bc6 100644 --- a/blockchain/modules/support/src/ecdp.rs +++ b/blockchain/modules/support/src/ecdp.rs @@ -1,209 +1,174 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 . - -use parity_scale_codec::FullCodec; -use primitives::ECDPPosition; -use sp_core::U256; -use sp_runtime::{DispatchError, DispatchResult}; -use sp_std::{ - cmp::{Eq, PartialEq}, - fmt::Debug, - prelude::*, -}; - -use crate::{dex::*, ExchangeRate, Ratio}; - -pub trait EmergencyShutdown { - fn is_shutdown() -> bool; -} - -pub trait AuctionManager { - type CurrencyId; - type Balance; - type AuctionId: FullCodec + Debug + Clone + Eq + PartialEq; - - fn new_collateral_auction( - refund_recipient: &AccountId, - currency_id: Self::CurrencyId, - amount: Self::Balance, - target: Self::Balance, - ) -> DispatchResult; - fn cancel_auction(id: Self::AuctionId) -> DispatchResult; - fn get_total_collateral_in_auction(id: Self::CurrencyId) -> Self::Balance; - fn get_total_target_in_auction() -> Self::Balance; -} - -pub trait PeggedRiskManager { - fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; - - fn check_position_valid( - currency_id: CurrencyId, - collateral_balance: Balance, - debit_balance: DebitBalance, - check_required_ratio: bool, - ) -> DispatchResult; - - fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; -} - -#[cfg(feature = "std")] -impl PeggedRiskManager - for () -{ - fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { - Default::default() - } - - fn check_position_valid( - _currency_id: CurrencyId, - _collateral_balance: Balance, - _debit_balance: DebitBalance, - _check_required_ratio: bool, - ) -> DispatchResult { - Ok(()) - } - - fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { - Ok(()) - } -} - -// pub trait UnpeggedRiskManager { -// fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; - -// fn check_position_valid( -// currency_id: CurrencyId, -// collateral_balance: Balance, -// debit_balance: DebitBalance, -// check_required_ratio: bool, -// ) -> DispatchResult; - -// fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; -// } - -// #[cfg(feature = "std")] -// impl UnpeggedRiskManager -// for () -// { -// fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { -// Default::default() -// } - -// fn check_position_valid( -// _currency_id: CurrencyId, -// _collateral_balance: Balance, -// _debit_balance: DebitBalance, -// _check_required_ratio: bool, -// ) -> DispatchResult { -// Ok(()) -// } - -// fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { -// Ok(()) -// } -// } - -/// An abstraction of cdp treasury for SlickUSD ECDP Protocol. -pub trait SlickUsdEcdpTreasury { - type Balance; - type CurrencyId; - - /// get surplus amount of cdp treasury - fn get_surplus_pool() -> Self::Balance; - - /// get debit amount of cdp treasury - fn get_debit_pool() -> Self::Balance; - - /// get collateral assets amount of cdp treasury - fn get_total_collaterals(id: Self::CurrencyId) -> Self::Balance; - - /// calculate the proportion of specific debit amount for the whole system - fn get_debit_proportion(amount: Self::Balance) -> Ratio; - - /// issue debit for cdp treasury - fn on_system_debit(amount: Self::Balance) -> DispatchResult; - - /// issue surplus(stable currency) for cdp treasury - fn on_system_surplus(amount: Self::Balance) -> DispatchResult; - - /// issue debit to `who` - /// if backed flag is true, means the debit to issue is backed on some - /// assets, otherwise will increase same amount of debit to system debit. - fn issue_debit(who: &AccountId, debit: Self::Balance, backed: bool) -> DispatchResult; - - /// burn debit(stable currency) of `who` - fn burn_debit(who: &AccountId, debit: Self::Balance) -> DispatchResult; - - /// deposit surplus(stable currency) to cdp treasury by `from` - fn deposit_surplus(from: &AccountId, surplus: Self::Balance) -> DispatchResult; - - /// withdraw surplus(stable currency) from cdp treasury to `to` - fn withdraw_surplus(to: &AccountId, surplus: Self::Balance) -> DispatchResult; - - /// deposit collateral assets to cdp treasury by `who` - fn deposit_collateral(from: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; - - /// withdraw collateral assets of cdp treasury to `who` - fn withdraw_collateral(to: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; -} - -pub trait SlickUsdEcdpTreasuryExtended: SlickUsdTreasury { - fn swap_collateral_to_stable( - currency_id: Self::CurrencyId, - limit: SwapLimit, - collateral_in_auction: bool, - ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; - - fn create_collateral_auctions( - currency_id: Self::CurrencyId, - amount: Self::Balance, - target: Self::Balance, - refund_receiver: AccountId, - splited: bool, - ) -> sp_std::result::Result; - - fn remove_liquidity_for_lp_collateral( - currency_id: Self::CurrencyId, - amount: Self::Balance, - ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; - - fn max_auction() -> u32; -} - -/// Functionality of SlickUSD ECDP Protocol to be exposed to EVM+. -pub trait SlickUsdEcdpManager { - /// Adjust ECDP loan - fn adjust_loan( - who: &AccountId, - currency_id: CurrencyId, - collateral_adjustment: Amount, - debit_adjustment: Amount, - ) -> DispatchResult; - /// Close ECDP loan using DEX - fn close_loan_by_dex(who: AccountId, currency_id: CurrencyId, max_collateral_amount: Balance) -> DispatchResult; - /// Get open ECDP corresponding to an account and collateral `CurrencyId` - fn get_position(who: &AccountId, currency_id: CurrencyId) -> ECDPPosition; - /// Get liquidation ratio for collateral `CurrencyId` - fn get_collateral_parameters(currency_id: CurrencyId) -> Vec; - /// Get current ratio of collateral to debit of open ECDP - fn get_current_collateral_ratio(who: &AccountId, currency_id: CurrencyId) -> Option; - /// Get exchange rate of debit units to debit value for a currency_id - fn get_debit_exchange_rate(currency_id: CurrencyId) -> ExchangeRate; -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 . + +use parity_scale_codec::FullCodec; +use primitives::ECDPPosition; +use sp_core::U256; +use sp_runtime::{DispatchError, DispatchResult}; +use sp_std::{ + cmp::{Eq, PartialEq}, + fmt::Debug, + prelude::*, +}; + +use crate::{dex::*, ExchangeRate, Ratio}; + +pub trait EmergencyShutdown { + fn is_shutdown() -> bool; +} + +pub trait AuctionsManager { + type CurrencyId; + type Balance; + type AuctionId: FullCodec + Debug + Clone + Eq + PartialEq; + + fn new_collateral_auction( + refund_recipient: &AccountId, + currency_id: Self::CurrencyId, + amount: Self::Balance, + target: Self::Balance, + ) -> DispatchResult; + fn cancel_auction(id: Self::AuctionId) -> DispatchResult; + fn get_total_collateral_in_auction(id: Self::CurrencyId) -> Self::Balance; + fn get_total_target_in_auction() -> Self::Balance; +} + +pub trait SlickUsdRiskManager { + fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; + + fn check_position_valid( + currency_id: CurrencyId, + collateral_balance: Balance, + debit_balance: DebitBalance, + check_required_ratio: bool, + ) -> DispatchResult; + + fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; +} + +#[cfg(feature = "std")] +impl SlickUsdRiskManager + for () +{ + fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { + Default::default() + } + + fn check_position_valid( + _currency_id: CurrencyId, + _collateral_balance: Balance, + _debit_balance: DebitBalance, + _check_required_ratio: bool, + ) -> DispatchResult { + Ok(()) + } + + fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { + Ok(()) + } +} + +/// An abstraction of cdp treasury for SlickUSD ECDP Protocol. +pub trait SlickUsdEcdpTreasury { + type Balance; + type CurrencyId; + + /// get surplus amount of cdp treasury + fn get_surplus_pool() -> Self::Balance; + + /// get debit amount of cdp treasury + fn get_debit_pool() -> Self::Balance; + + /// get collateral assets amount of cdp treasury + fn get_total_collaterals(id: Self::CurrencyId) -> Self::Balance; + + /// calculate the proportion of specific debit amount for the whole system + fn get_debit_proportion(amount: Self::Balance) -> Ratio; + + /// issue debit for cdp treasury + fn on_system_debit(amount: Self::Balance) -> DispatchResult; + + /// issue surplus(USSD) for cdp treasury + fn on_system_surplus(amount: Self::Balance) -> DispatchResult; + + /// issue debit to `who` + /// if backed flag is true, means the debit to issue is backed on some + /// assets, otherwise will increase same amount of debit to system debit. + fn issue_debit(who: &AccountId, debit: Self::Balance, backed: bool) -> DispatchResult; + + /// burn debit(USSD) of `who` + fn burn_debit(who: &AccountId, debit: Self::Balance) -> DispatchResult; + + /// deposit surplus(USSD) to cdp treasury by `from` + fn deposit_surplus(from: &AccountId, surplus: Self::Balance) -> DispatchResult; + + /// withdraw surplus(USSD) from cdp treasury to `to` + fn withdraw_surplus(to: &AccountId, surplus: Self::Balance) -> DispatchResult; + + /// deposit collateral assets to cdp treasury by `who` + fn deposit_collateral(from: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; + + /// withdraw collateral assets of cdp treasury to `who` + fn withdraw_collateral(to: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; +} + +pub trait SlickUsdEcdpTreasuryExtended: SlickUsdTreasury { + fn swap_collateral_to_ussd( + currency_id: Self::CurrencyId, + limit: SwapLimit, + collateral_in_auction: bool, + ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; + + fn create_collateral_auctions( + currency_id: Self::CurrencyId, + amount: Self::Balance, + target: Self::Balance, + refund_receiver: AccountId, + splited: bool, + ) -> sp_std::result::Result; + + fn remove_liquidity_for_lp_collateral( + currency_id: Self::CurrencyId, + amount: Self::Balance, + ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; + + fn max_auction() -> u32; +} + +/// Functionality of SlickUSD ECDP Protocol to be exposed to EVM+. +pub trait SlickUsdEcdpManager { + /// Adjust ECDP loan + fn adjust_loan( + who: &AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult; + /// Close ECDP loan using DEX + fn close_loan_by_dex(who: AccountId, currency_id: CurrencyId, max_collateral_amount: Balance) -> DispatchResult; + /// Get open ECDP corresponding to an account and collateral `CurrencyId` + fn get_position(who: &AccountId, currency_id: CurrencyId) -> ECDPPosition; + /// Get liquidation ratio for collateral `CurrencyId` + fn get_collateral_parameters(currency_id: CurrencyId) -> Vec; + /// Get current ratio of collateral to debit of open ECDP + fn get_current_collateral_ratio(who: &AccountId, currency_id: CurrencyId) -> Option; + /// Get exchange rate of debit units to debit value for a currency_id + fn get_debit_exchange_rate(currency_id: CurrencyId) -> ExchangeRate; +} diff --git a/blockchain/modules/support/src/lib.rs b/blockchain/modules/support/src/lib.rs index 4bff5e59..ba2871a9 100644 --- a/blockchain/modules/support/src/lib.rs +++ b/blockchain/modules/support/src/lib.rs @@ -158,7 +158,7 @@ pub trait LiquidateCollateral { who: &AccountId, currency_id: CurrencyId, amount: Balance, - target_stable_amount: Balance, + target_ussd_amount: Balance, ) -> DispatchResult; } @@ -168,11 +168,11 @@ impl LiquidateCollateral for Tuple { who: &AccountId, currency_id: CurrencyId, amount: Balance, - target_stable_amount: Balance, + target_ussd_amount: Balance, ) -> DispatchResult { let mut last_error = None; for_tuples!( #( - match Tuple::liquidate(who, currency_id, amount, target_stable_amount) { + match Tuple::liquidate(who, currency_id, amount, target_ussd_amount) { Ok(_) => return Ok(()), Err(e) => { last_error = Some(e) } }