diff --git a/Cargo.lock b/Cargo.lock index c52dc17..db0d5d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7455,6 +7455,30 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-liquidation" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "mp-system", + "pallet-assets", + "pallet-balances", + "pallet-order", + "pallet-pot", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "smallvec", + "sp-consensus-aura", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-membership" version = "4.0.0-dev" @@ -8225,7 +8249,7 @@ dependencies = [ [[package]] name = "parachain-magnet-node" -version = "0.2.0" +version = "0.3.0" dependencies = [ "array-bytes", "clap", @@ -8321,7 +8345,7 @@ dependencies = [ [[package]] name = "parachain-magnet-runtime" -version = "0.2.0" +version = "0.3.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -8366,6 +8390,7 @@ dependencies = [ "pallet-evm-precompile-simple", "pallet-evm-utils", "pallet-hotfix-sufficients", + "pallet-liquidation", "pallet-motion", "pallet-order", "pallet-parachain-template", diff --git a/Cargo.toml b/Cargo.toml index d774d2d..02745a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "pallets/pot", "pallets/assurance", "pallets/pallet-xcm", + "pallets/liquidation", ] resolver = "2" diff --git a/node/Cargo.toml b/node/Cargo.toml index a4b6334..b1be2f4 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "parachain-magnet-node" -version = "0.2.0" +version = "0.3.0" authors = ["Anonymous"] description = "A scalable evm smart contract platform node, utilizing DOT as the gas fee." license = "Apache License 2.0" diff --git a/pallets/liquidation/Cargo.toml b/pallets/liquidation/Cargo.toml new file mode 100644 index 0000000..308dde1 --- /dev/null +++ b/pallets/liquidation/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "pallet-liquidation" +version = "0.1.0" +authors = ["Magport Tech."] +description = "gas fee caculate and liquidation" +license = "Apache-2.0" +edition.workspace = true +repository.workspace = true +homepage = "https://magnet.magport.io/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = {package = "parity-scale-codec", version = "3.6.4", default-features = false, features = [ + "derive", +]} +log = {version = "0.4.20", default-features = false} +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +smallvec = "1.11.0" + +pallet-order = {path = "../order", default-features = false} +pallet-pot = {path = "../pot", default-features = false} +mp-system = { path = "../../primitives/system", default-features = false } + +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +pallet-assets = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false} +frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false, optional = true} + +[features] +default = ['std'] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-assets/std", + "pallet-order/std", + "pallet-pot/std", + "mp-system/std", + "frame-benchmarking/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/pallets/liquidation/src/lib.rs b/pallets/liquidation/src/lib.rs new file mode 100644 index 0000000..7a7bfc6 --- /dev/null +++ b/pallets/liquidation/src/lib.rs @@ -0,0 +1,437 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +use frame_support::{ + storage::types::StorageMap, + traits::{Currency, ExistenceRequirement, Get}, + weights::WeightToFeePolynomial, + Twox64Concat, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use mp_system::BASE_ACCOUNT; +pub use pallet::*; +use sp_runtime::{traits::Zero, AccountId32, Perbill, Saturating}; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +pub type Balance = u128; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use pallet_order::OrderGasCost; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config + pallet_order::Config + pallet_pot::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + ///handle transfer + type Currency: frame_support::traits::Currency + + frame_support::traits::ReservableCurrency; + + ///Handles converting weight to fee value + type WeightToFee: WeightToFeePolynomial; + + ///get real weight cost from coreTime placeOrder pallet + type OrderGasCost: OrderGasCost; + + ///profit distribute ratio to treasury account + #[pallet::constant] + type SystemRatio: Get; + + ///profit distribute ratio to treasury account + #[pallet::constant] + type TreasuryRatio: Get; + + /// profit distribute ratio to operation account + #[pallet::constant] + type OperationRatio: Get; + + /// ED necessitate the account to exist + #[pallet::constant] + type ExistentialDeposit: Get; + + /// system accountId + #[pallet::constant] + type SystemAccountName: Get<&'static str>; + + /// treasury accountId + #[pallet::constant] + type TreasuryAccountName: Get<&'static str>; + + /// operation accountId + #[pallet::constant] + type OperationAccountName: Get<&'static str>; + + ///how many blocks to distribute a profit distribution + #[pallet::constant] + type ProfitDistributionCycle: Get>; + } + + #[pallet::storage] + #[pallet::getter(fn base_account_balance)] + pub type BaseAccountReserved = StorageValue<_, Balance, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn collator_real_gas_costs)] + pub type CollatorRealGasCosts = + StorageMap<_, Twox64Concat, T::AccountId, Balance, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn total_income)] + pub type TotalIncome = StorageValue<_, Balance, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn total_cost)] + pub type TotalCost = StorageValue<_, Balance, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn block_count)] + pub type DistributionBlockCount = StorageValue<_, BlockNumberFor, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// parameters. [blockNumber, blockFee, orderCost, collator] + /// block has been handled include weight and fee + BlockProcessed(BlockNumberFor, BalanceOf, Balance, T::AccountId), + + /// parameters. [accountId, balance] + ///transfer profit from BaseAccount to SystemAccount + SystemAccountProfit(T::AccountId, BalanceOf), + + /// parameters. [accountId, balance] + ///transfer total fee from BaseAccount to SystemAccount + TransferBaseToSystem(T::AccountId, T::AccountId, BalanceOf), + + /// parameters. [accountId, balance] + ///transfer profit from BaseAccount to TreasuryAccount + TreasuryAccountProfit(T::AccountId, BalanceOf), + + /// parameters. [accountId, balance] + ///transfer profit from BaseAccount to operationAccount + OperationAccountProfit(T::AccountId, BalanceOf), + + /// parameters. [accountId, balance] + ///transfer profit from BaseAccount to collators account + CollatorProfit(T::AccountId, BalanceOf), + + /// parameters. [accountId, balance] + ///transfer principal from BaseAccount to system account if deficit + CollatorPrincipal(T::AccountId, BalanceOf), + + /// parameters. [accountId, balance] + ///transfer from SystemAccount to collators account for compensate + CollatorCompensate(T::AccountId, BalanceOf), + + /// profit distributed succeed + ProfitDistributed, + + /// collators compensated + CollatorsCompensated, + + /// error occurred + Error(Error), + } + + #[pallet::error] + #[derive(Clone, PartialEq, Eq)] + pub enum Error { + /// get real gas cost failed + FailedToFetchRealGasCost, + + /// internal errors + InternalError, + + ///get pot account errors + PotAccountError, + + ///failed to process liquidation + ProcessLiquidationError, + } + + #[pallet::hooks] + impl Hooks> for Pallet + where + T::AccountId: From, + { + fn on_finalize(n: BlockNumberFor) { + let base_account = ::from(BASE_ACCOUNT); + let base_account_balance = ::Currency::free_balance(&base_account); + + let (collator, real_gas_cost) = match T::OrderGasCost::gas_cost(n) { + Some((collator, real_gas_cost)) => (collator, real_gas_cost), + None => { + Self::deposit_event(Event::Error(Error::::FailedToFetchRealGasCost.into())); + return; + }, + }; + + CollatorRealGasCosts::::mutate(collator.clone(), |cost| { + *cost = cost.saturating_add(real_gas_cost); + }); + + let reserved_balance: BalanceOf = + T::ExistentialDeposit::get().try_into().unwrap_or_else(|_| Zero::zero()); + + let block_fee_except_ed = base_account_balance.saturating_sub(reserved_balance); + let current_block_fee_u128: Balance = + block_fee_except_ed.try_into().unwrap_or_else(|_| 0); + + let base_account = ::from(BASE_ACCOUNT); + let system_account = + match pallet_pot::Pallet::::ensure_pot(T::SystemAccountName::get()) { + Ok(account) => account, + Err(err) => { + log::error!("get system account err:{:?}", err); + Self::deposit_event(Event::Error(Error::::InternalError.into())); + return; + }, + }; + + match Self::transfer_funds(&base_account, &system_account, block_fee_except_ed.clone()) + { + Ok(_) => { + Self::deposit_event(Event::TransferBaseToSystem( + base_account.clone(), + system_account.clone(), + block_fee_except_ed.clone(), + )); + }, + Err(err) => { + log::error!("Transfer to system account failed: {:?}", err); + Self::deposit_event(Event::Error(Error::::InternalError.into())); + return; + }, + } + + TotalIncome::::mutate(|income| { + *income = income.saturating_add(current_block_fee_u128) + }); + TotalCost::::mutate(|cost| *cost = cost.saturating_add(real_gas_cost)); + + let mut count = DistributionBlockCount::::get(); + count = count.saturating_add(1u32.into()); + DistributionBlockCount::::put(count); + if count % T::ProfitDistributionCycle::get() == Zero::zero() { + DistributionBlockCount::::put(BlockNumberFor::::zero()); + match Self::distribute_profit() { + Ok(_) => { + Self::deposit_event(Event::BlockProcessed( + n, + block_fee_except_ed.clone(), + real_gas_cost, + collator, + )); + }, + Err(err) => { + log::error!("process liquidation failed: {:?}", err); + Self::deposit_event(Event::Error( + Error::::ProcessLiquidationError.into(), + )); + }, + } + } + } + } + + impl Pallet + where + T::AccountId: From, + { + fn transfer_funds( + source: &T::AccountId, + to: &T::AccountId, + amount: BalanceOf, + ) -> DispatchResult { + ensure!( + ::Currency::free_balance(source) >= amount, + "Not enough balance" + ); + ::Currency::transfer( + source, + to, + amount, + ExistenceRequirement::KeepAlive, + )?; + + Ok(()) + } + + fn distribute_profit() -> DispatchResult { + let total_income = TotalIncome::::get(); + let total_cost = TotalCost::::get(); + + if total_income > total_cost { + Self::distribute_positive_profit()?; + Self::deposit_event(Event::ProfitDistributed); + } else { + Self::compensate_collators()?; + Self::deposit_event(Event::CollatorsCompensated); + } + + let _ = >::clear(u32::max_value(), None); + TotalIncome::::put(0u128); + TotalCost::::put(0u128); + + Ok(()) + } + + #[cfg(test)] + pub fn test_distribute_profit() -> DispatchResult { + Self::distribute_profit() + } + + fn distribute_positive_profit() -> DispatchResult { + let total_income = TotalIncome::::get(); + let total_cost = TotalCost::::get(); + let total_profit = total_income.saturating_sub(total_cost); + + let system_account = + match pallet_pot::Pallet::::ensure_pot(T::SystemAccountName::get()) { + Ok(account) => account, + Err(err) => { + log::error!("get system account err:{:?}", err); + Err(Error::::PotAccountError)? + }, + }; + let treasury_account = + match pallet_pot::Pallet::::ensure_pot(T::TreasuryAccountName::get()) { + Ok(account) => account, + Err(err) => { + log::error!("get treasury account err:{:?}", err); + Err(Error::::PotAccountError)? + }, + }; + let operation_account = + match pallet_pot::Pallet::::ensure_pot(T::OperationAccountName::get()) { + Ok(account) => account, + Err(err) => { + log::error!("get maintenance account err:{:?}", err); + Err(Error::::PotAccountError)? + }, + }; + + let system_ratio = T::SystemRatio::get(); + let treasury_ratio = T::TreasuryRatio::get(); + let operation_ratio = T::OperationRatio::get(); + + let treasury_amount = treasury_ratio * total_profit; + let operation_amount = operation_ratio * total_profit; + let system_amount = system_ratio * total_profit; + let total_collators_profit = + total_profit.saturating_sub(treasury_amount + operation_amount + system_amount); + + let treasury_account_profit = + treasury_amount.try_into().unwrap_or_else(|_| Zero::zero()); + match Self::transfer_funds(&system_account, &treasury_account, treasury_account_profit) + { + Ok(_) => { + Self::deposit_event(Event::TreasuryAccountProfit( + treasury_account.clone(), + treasury_account_profit, + )); + }, + Err(err) => { + log::error!("Transfer to treasury account failed: {:?}", err); + }, + } + + let operation_account_profit = + operation_amount.try_into().unwrap_or_else(|_| Zero::zero()); + match Self::transfer_funds( + &system_account, + &operation_account, + operation_account_profit, + ) { + Ok(_) => { + Self::deposit_event(Event::OperationAccountProfit( + operation_account.clone(), + operation_account_profit, + )); + }, + Err(err) => { + log::error!("Transfer to maintenance account failed: {:?}", err); + }, + } + + // distribute profit and compensate cost to every collator + for (collator, collator_cost) in CollatorRealGasCosts::::iter() { + let collator_ratio = Perbill::from_rational(collator_cost, total_cost); + let collator_profit = collator_ratio * total_collators_profit; + + let collator_addr_profit = + collator_profit.try_into().unwrap_or_else(|_| Zero::zero()); + match Self::transfer_funds(&system_account, &collator.clone(), collator_addr_profit) + { + Ok(_) => { + Self::deposit_event(Event::CollatorProfit( + collator.clone(), + collator_addr_profit, + )); + }, + Err(err) => { + log::error!("Transfer profit to collator account failed: {:?}", err); + }, + } + + let collator_addr_cost = collator_cost.try_into().unwrap_or_else(|_| Zero::zero()); + match Self::transfer_funds(&system_account, &collator.clone(), collator_addr_cost) { + Ok(_) => { + Self::deposit_event(Event::CollatorCompensate( + collator.clone(), + collator_addr_cost, + )); + }, + Err(err) => { + log::error!("Transfer principal to collator account failed: {:?}", err); + }, + } + } + + Ok(()) + } + + fn compensate_collators() -> DispatchResult { + let system_account = + match pallet_pot::Pallet::::ensure_pot(T::SystemAccountName::get()) { + Ok(account) => account, + Err(err) => { + log::error!("get system account err:{:?}", err); + Err(Error::::PotAccountError)? + }, + }; + + // compensate for every collator + for (collator, collator_cost) in CollatorRealGasCosts::::iter() { + let collator_addr_cost = collator_cost.try_into().unwrap_or_else(|_| Zero::zero()); + match Self::transfer_funds(&system_account, &collator.clone(), collator_addr_cost) { + Ok(_) => { + Self::deposit_event(Event::CollatorCompensate( + collator, + collator_addr_cost, + )); + }, + Err(err) => { + log::error!("Transfer principal to collator account failed: {:?}", err); + }, + } + } + + Ok(()) + } + } +} diff --git a/pallets/liquidation/src/mock.rs b/pallets/liquidation/src/mock.rs new file mode 100644 index 0000000..5bbb437 --- /dev/null +++ b/pallets/liquidation/src/mock.rs @@ -0,0 +1,222 @@ +pub(crate) use crate as pallet_liquidation; +pub(crate) use crate::Event as LiquidationEvent; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, + weights::{ + constants::ExtrinsicBaseWeight, WeightToFeeCoefficient, WeightToFeeCoefficients, + WeightToFeePolynomial, + }, +}; +use frame_system as system; +use smallvec::smallvec; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, BuildStorage, Perbill, +}; + +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_order::OrderGasCost; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_std::collections::btree_map::BTreeMap; + +type Balance = u128; +type BlockNumber = u32; +type Block = frame_system::mocking::MockBlock; + +pub const UNIT: Balance = 1_000_000_000_000_000_000; +const MILLIUNIT: Balance = 1_000_000_000; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + OrderPallet: pallet_order::{Pallet, Storage, Event}, + Pot: pallet_pot::{Pallet, Call, Storage, Event}, + Liquidation: pallet_liquidation::{Pallet, Storage, Event}, + } +); + +//ALICE +const COLLATOR_BYTES: [u8; 32] = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, + 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, +]; +const SYSTEM_ACCOUNT_BYTES: [u8; 32] = [ + 54, 99, 32, 239, 79, 115, 118, 121, 15, 239, 57, 41, 2, 255, 91, 189, 21, 193, 175, 83, 111, + 196, 75, 126, 82, 14, 205, 184, 6, 168, 148, 234, +]; + +const COLLATOR: AccountId32 = AccountId32::new(COLLATOR_BYTES); +const SYSTEM_ACCOUNT: AccountId32 = AccountId32::new(SYSTEM_ACCOUNT_BYTES); + +/// The existential deposit. Set to 1/10 of the Connected Relay Chain. +pub const EXISTENTIAL_DEPOSIT: Balance = MILLIUNIT; + +parameter_types! { + pub const SystemRatio: Perbill = Perbill::from_percent(20); // 20% for system + pub const TreasuryRatio: Perbill = Perbill::from_percent(33); // 33% for treasury + pub const OperationRatio: Perbill = Perbill::from_percent(25); // 25% for maintenance + pub const ProfitDistributionCycle: BlockNumber = 10; + pub const ExistDeposit: Balance = EXISTENTIAL_DEPOSIT; + pub const SystemAccountName: &'static str = "system"; + pub const TreasuryAccountName: &'static str = "treasury"; + pub const OperationAccountName: &'static str = "maintenance"; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<5>; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type RuntimeOrigin = RuntimeOrigin; +} + +parameter_types! { + pub const ExistentialDeposit: u128 = EXISTENTIAL_DEPOSIT; +} + +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = (); + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +parameter_types! { + pub const SlotWidth: u32 = 2; + pub const OrderMaxAmount:Balance = 200000000; + pub const TxPoolThreshold:Balance = 3000000000; +} + +impl pallet_order::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AuthorityId = AuraId; + type Currency = Balances; + type UpdateOrigin = frame_system::EnsureRoot; + type OrderMaxAmount = OrderMaxAmount; + type SlotWidth = SlotWidth; + type TxPoolThreshold = TxPoolThreshold; + type WeightInfo = (); +} + +use pallet_pot::PotNameBtreemap; +pub type AccountId = AccountId32; +parameter_types! { + pub const PotNames: [&'static str;3] = ["system", "treasury", "maintenance"]; + pub Pots: BTreeMap = pallet_pot + ::HashedPotNameBtreemap + ::> + ::pots_btreemap(&(PotNames::get())); +} +impl pallet_pot::Config for Test { + type RuntimeEvent = RuntimeEvent; + type PotNameMapping = pallet_pot::HashedPotNameMapping; + type Currency = Balances; + type Pots = Pots; +} + +impl pallet_liquidation::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type WeightToFee = WeightToFee; + type OrderGasCost = MockOrderGasCostHandler; + type SystemRatio = SystemRatio; + type TreasuryRatio = TreasuryRatio; + type OperationRatio = OperationRatio; + type ExistentialDeposit = ExistDeposit; + type SystemAccountName = SystemAccountName; + type TreasuryAccountName = TreasuryAccountName; + type OperationAccountName = OperationAccountName; + type ProfitDistributionCycle = ProfitDistributionCycle; +} + +pub struct WeightToFee; +impl WeightToFeePolynomial for WeightToFee { + type Balance = Balance; + fn polynomial() -> WeightToFeeCoefficients { + let p = MILLIUNIT / 10; + let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); + smallvec![WeightToFeeCoefficient { + degree: 1, + negative: false, + coeff_frac: Perbill::from_rational(p % q, q), + coeff_integer: p / q, + }] + } +} + +pub struct MockOrderGasCostHandler; +impl OrderGasCost for MockOrderGasCostHandler +where + T: pallet_order::Config, + T::AccountId: From<[u8; 32]>, +{ + fn gas_cost(_block_number: BlockNumberFor) -> Option<(T::AccountId, Balance)> { + let account = T::AccountId::try_from(COLLATOR_BYTES).unwrap(); + Some((account, 10000000 as u128)) + } +} + +pub struct ExtBuilder { + existential_deposit: u128, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { existential_deposit: 1 } + } +} + +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u128) -> Self { + self.existential_deposit = existential_deposit; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let balances_config = pallet_balances::GenesisConfig:: { + balances: vec![(COLLATOR, UNIT), (SYSTEM_ACCOUNT, UNIT)], + }; + balances_config.assimilate_storage(&mut storage).unwrap(); + + let mut ext = sp_io::TestExternalities::new(storage); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} + +pub(crate) fn expected_event(event: &LiquidationEvent) -> bool { + matches!(event, LiquidationEvent::ProfitDistributed | LiquidationEvent::CollatorsCompensated) +} diff --git a/pallets/liquidation/src/tests.rs b/pallets/liquidation/src/tests.rs new file mode 100644 index 0000000..a1d30a0 --- /dev/null +++ b/pallets/liquidation/src/tests.rs @@ -0,0 +1,31 @@ +use super::*; +use crate::mock::*; +use frame_support::assert_ok; +use sp_runtime::traits::Zero; + +#[test] +fn distribute_profit_should_work() { + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + for n in 1..=::ProfitDistributionCycle::get() { + frame_system::Pallet::::set_block_number(n.into()); + } + + assert_ok!(Liquidation::test_distribute_profit()); + let events = frame_system::Pallet::::events() + .into_iter() + .map(|record| record.event) + .filter_map(|event| { + if let RuntimeEvent::Liquidation(inner_event) = event { + Some(inner_event) + } else { + None + } + }) + .collect::>(); + + assert!(events.iter().any(expected_event)); + assert_eq!(>::get(), Zero::zero()); + assert_eq!(>::get(), Zero::zero()); + assert!(>::iter().next().is_none()); + }); +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 3e75ec4..63cfa77 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "parachain-magnet-runtime" -version = "0.2.0" +version = "0.3.0" authors = ["Anonymous"] description = "A scalable evm smart contract platform runtime, utilizing DOT as the gas fee." license = "Apache License 2.0" @@ -34,6 +34,7 @@ pallet-pot = { path = "../pallets/pot", default-features = false } pallet-pot-runtime-api = { path = "../pallets/pot/runtime-api", default-features = false } pallet-assurance = { path = "../pallets/assurance", default-features = false } pallet-xcm = { path = "../pallets/pallet-xcm", default-features = false} +pallet-liquidation = {path = "../pallets/liquidation", default-features = false} # Substrate frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false, optional = true} @@ -98,14 +99,13 @@ pallet-dynamic-fee = { version = "4.0.0-dev", git = "https://github.com/parityte pallet-evm-precompile-modexp = { version = "2.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false } pallet-evm-precompile-sha3fips = { version = "2.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false } pallet-evm-precompile-simple = { version = "2.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false } -#pallet-evm-test-vector-support = { version = "1.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false} pallet-hotfix-sufficients = { version = "1.0.0", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false } # Frontier Primitive fp-evm = { version = "3.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false } fp-account = { version = "1.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false } fp-dynamic-fee = { version = "1.0.0", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false } fp-rpc = { version = "3.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false } -fp-self-contained = { version = "1.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false, features = ["serde"] } +fp-self-contained = { version = "1.0.0-dev", git = "https://github.com/paritytech/frontier", branch = "polkadot-v1.1.0", default-features = false, features = ["serde", "try-runtime"] } [features] default = ["std", "with-rocksdb-weights"] @@ -185,6 +185,7 @@ std = [ "pallet-order/std", "sp-trie/std", "cumulus-primitives-timestamp/std", + "pallet-liquidation/std", ] runtime-benchmarks = [ @@ -214,14 +215,17 @@ runtime-benchmarks = [ "pallet-society/runtime-benchmarks", "pallet-evm/runtime-benchmarks", "pallet-ethereum/runtime-benchmarks", + "pallet-liquidation/runtime-benchmarks", ] try-runtime = [ + "cumulus-pallet-parachain-system/try-runtime", "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-dmp-queue/try-runtime", "cumulus-pallet-parachain-system/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", + "cumulus-pallet-xcm/try-runtime", "frame-executive/try-runtime", "frame-support/try-runtime", "frame-system/try-runtime", @@ -242,6 +246,20 @@ try-runtime = [ "pallet-evm-utils/try-runtime", "pallet-precompile-substrate-utils/try-runtime", "pallet-order/try-runtime", + "pallet-liquidation/try-runtime", + "pallet-assets/try-runtime", + "pallet-assets-bridge/try-runtime", + "pallet-collective/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-evm-chain-id/try-runtime", + "pallet-evm/try-runtime", + "pallet-ethereum/try-runtime", + "pallet-base-fee/try-runtime", + "pallet-dynamic-fee/try-runtime", + "pallet-hotfix-sufficients/try-runtime", + "pallet-pot/try-runtime", + "pallet-assurance/try-runtime", + "pallet-motion/try-runtime", ] experimental = [ "pallet-aura/experimental" ] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ba9b42e..0e3e08b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -16,7 +16,7 @@ use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use smallvec::smallvec; use sp_api::impl_runtime_apis; use sp_core::{ - crypto::{ByteArray, KeyTypeId}, + crypto::{AccountId32, ByteArray, KeyTypeId}, OpaqueMetadata, H160, H256, U256, }; use sp_runtime::{ @@ -94,8 +94,7 @@ use pallet_ethereum::{ TransactionData, }; use pallet_evm::{ - Account as EVMAccount, EVMCurrencyAdapter, EnsureAddressTruncated, FeeCalculator, - HashedAddressMapping, OnChargeEVMTransaction, Runner, + Account as EVMAccount, EnsureAddressTruncated, FeeCalculator, HashedAddressMapping, Runner, }; mod precompiles; @@ -488,6 +487,39 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = ConstU32<0>; } +use pallet_liquidation; +pub struct MagnetToStakingPot(PhantomData); +impl OnUnbalanced> for MagnetToStakingPot +where + R: pallet_balances::Config, + AccountIdOf: From + Into, + ::RuntimeEvent: From>, + R::AccountId: From, +{ + fn on_nonzero_unbalanced(amount: NegativeImbalance) { + let staking_pot = mp_system::BASE_ACCOUNT; + >::resolve_creating(&staking_pot.into(), amount); + } +} + +pub struct MagnetDealWithFees(PhantomData); +impl OnUnbalanced> for MagnetDealWithFees +where + R: pallet_balances::Config, + AccountIdOf: From + Into, + ::RuntimeEvent: From>, + R::AccountId: From, +{ + fn on_unbalanceds(mut fees_then_tips: impl Iterator>) { + if let Some(mut fees) = fees_then_tips.next() { + if let Some(tips) = fees_then_tips.next() { + tips.merge_into(&mut fees); + } + as OnUnbalanced<_>>::on_unbalanced(fees); + } + } +} + parameter_types! { /// Relay Chain `TransactionByteFee` / 10 pub const TransactionByteFee: Balance = 10 * MICROUNIT; @@ -495,7 +527,8 @@ parameter_types! { impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; + type OnChargeTransaction = + pallet_transaction_payment::CurrencyAdapter>; type WeightToFee = WeightToFee; type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; @@ -702,64 +735,6 @@ impl> FindAuthor for FindAuthorTruncated { } } -/// Handles transaction fees from the EVM, depositing priority fee in a staking pot -pub struct EVMDealWithFees(PhantomData); - -impl OnUnbalanced> for EVMDealWithFees -where - R: pallet_balances::Config + pallet_collator_selection::Config + core::fmt::Debug, - AccountIdOf: From + Into, - ::RuntimeEvent: From>, -{ - fn on_nonzero_unbalanced(amount: NegativeImbalance) { - // deposit the fee into the collator_selection reward pot - let staking_pot = >::account_id(); - >::resolve_creating(&staking_pot, amount); - } -} - -pub struct EVMTransactionChargeHandler(PhantomData); - -type BalanceOf = <::Currency as Currency>>::Balance; -type PositiveImbalanceOf = - <::Currency as Currency>>::PositiveImbalance; -type NegativeImbalanceOf = - <::Currency as Currency>>::NegativeImbalance; - -impl OnChargeEVMTransaction for EVMTransactionChargeHandler -where - R: pallet_evm::Config, - PositiveImbalanceOf: Imbalance, Opposite = NegativeImbalanceOf>, - NegativeImbalanceOf: Imbalance, Opposite = PositiveImbalanceOf>, - OU: OnUnbalanced>, - U256: UniqueSaturatedInto>, -{ - type LiquidityInfo = Option>; - - fn withdraw_fee( - who: &H160, - fee: sp_core::U256, - ) -> Result> { - EVMCurrencyAdapter::<::Currency, ()>::withdraw_fee(who, fee) - } - - fn correct_and_deposit_fee( - who: &H160, - corrected_fee: sp_core::U256, - base_fee: sp_core::U256, - already_withdrawn: Self::LiquidityInfo, - ) -> Self::LiquidityInfo { - ::Currency, OU> - as OnChargeEVMTransaction>::correct_and_deposit_fee(who, corrected_fee, base_fee, already_withdrawn) - } - - fn pay_priority_fee(tip: Self::LiquidityInfo) { - if let Some(tip) = tip { - OU::on_unbalanced(tip); - } - } -} - const BLOCK_GAS_LIMIT: u64 = 75_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; @@ -777,7 +752,6 @@ impl pallet_evm::Config for Runtime { type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; type CallOrigin = EnsureAddressTruncated; type WithdrawOrigin = EnsureAddressTruncated; - //type AddressMapping = IdentityAddressMapping; type AddressMapping = HashedAddressMapping; type Currency = Balances; type RuntimeEvent = RuntimeEvent; @@ -786,8 +760,7 @@ impl pallet_evm::Config for Runtime { type ChainId = EVMChainId; type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; - //type OnChargeTransaction = (); - type OnChargeTransaction = EVMTransactionChargeHandler>; + type OnChargeTransaction = (); type OnCreate = (); type FindAuthor = FindAuthorTruncated; type GasLimitPovSizeRatio = GasLimitPovSizeRatio; @@ -848,40 +821,6 @@ pub const fn deposit(items: u32, bytes: u32) -> Balance { (items as Balance * 20 * UNIT + (bytes as Balance) * 100 * MICROUNIT) / 100 } -/* -parameter_types! { - pub const AssetDeposit: Balance = 10 * UNIT; - pub const AssetAccountDeposit: Balance = deposit(1, 16); - pub const ApprovalDeposit: Balance = EXISTENTIAL_DEPOSIT; - pub const StringLimit: u32 = 50; - pub const MetadataDepositBase: Balance = deposit(1, 68); - pub const MetadataDepositPerByte: Balance = deposit(0, 1); -} - -impl pallet_assets::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Balance = Balance; - type RemoveItemsLimit = ConstU32<1000>; - type AssetId = u32; - type AssetIdParameter = codec::Compact; - type Currency = Balances; - type CreateOrigin = AsEnsureOriginWithArg>; - type ForceOrigin = EnsureRoot; - type AssetDeposit = AssetDeposit; - type AssetAccountDeposit = AssetAccountDeposit; - type MetadataDepositBase = MetadataDepositBase; - type MetadataDepositPerByte = MetadataDepositPerByte; - type ApprovalDeposit = ApprovalDeposit; - type StringLimit = StringLimit; - type Freezer = (); - type Extra = (); - type CallbackHandle = (); - type WeightInfo = pallet_assets::weights::SubstrateWeight; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); -} -*/ - parameter_types! { pub const AssetDeposit: Balance = 10 * UNIT; // 10 UNITS deposit to create fungible asset class pub const AssetAccountDeposit: Balance = deposit(1, 16); @@ -958,6 +897,32 @@ impl pallet_assurance::Config for Runtime { type DefaultLiquidateThreshold = ConstU128<0>; } +parameter_types! { + pub const SystemRatio: Perbill = Perbill::from_percent(20); // 20% for system + pub const TreasuryRatio: Perbill = Perbill::from_percent(33); // 33% for treasury + pub const OperationRatio: Perbill = Perbill::from_percent(25); // 25% for maintenance + pub const ProfitDistributionCycle: BlockNumber = 10; + pub const ExistDeposit: Balance = EXISTENTIAL_DEPOSIT; + pub const SystemAccountName: &'static str = "system"; + pub const TreasuryAccountName: &'static str = "treasury"; + pub const OperationAccountName: &'static str = "maintenance"; +} + +impl pallet_liquidation::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type WeightToFee = WeightToFee; + type OrderGasCost = OrderGasCostHandler; + type SystemRatio = SystemRatio; + type TreasuryRatio = TreasuryRatio; + type OperationRatio = OperationRatio; + type ExistentialDeposit = ExistDeposit; + type SystemAccountName = SystemAccountName; + type TreasuryAccountName = TreasuryAccountName; + type OperationAccountName = OperationAccountName; + type ProfitDistributionCycle = ProfitDistributionCycle; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -1008,6 +973,7 @@ construct_runtime!( EVMUtils: pallet_evm_utils = 60, Pot: pallet_pot = 61, Assurance: pallet_assurance = 62, + Liquidation: pallet_liquidation = 63, } ); diff --git a/runtime/src/xcm_config.rs b/runtime/src/xcm_config.rs index 8425e9d..4f87096 100644 --- a/runtime/src/xcm_config.rs +++ b/runtime/src/xcm_config.rs @@ -185,8 +185,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = ();