From e33a964dc1551fbde1858643c70325e5124e2f88 Mon Sep 17 00:00:00 2001
From: wangjj9219 <183318287@qq.com>
Date: Sun, 28 Nov 2021 17:10:16 +0800
Subject: [PATCH 01/27] refactor homa(WIP)
---
Cargo.lock | 31 +-
modules/homa/Cargo.toml | 45 +-
modules/homa/src/lib.rs | 982 +++++++++++++++++++--
modules/homa/src/weights.rs | 115 ---
modules/relaychain/src/lib.rs | 19 +-
modules/support/src/lib.rs | 10 +
node/service/src/chain_spec/mandala.rs | 25 +-
runtime/karura/src/lib.rs | 4 +-
runtime/mandala/Cargo.toml | 11 -
runtime/mandala/src/lib.rs | 86 +-
runtime/mandala/src/weights/mod.rs | 1 -
runtime/mandala/src/weights/module_homa.rs | 75 --
12 files changed, 997 insertions(+), 407 deletions(-)
delete mode 100644 modules/homa/src/weights.rs
delete mode 100644 runtime/mandala/src/weights/module_homa.rs
diff --git a/Cargo.lock b/Cargo.lock
index 0691b7dc96..f542f7a740 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4884,19 +4884,15 @@ dependencies = [
"module-evm-utiltity",
"module-homa",
"module-homa-lite",
- "module-homa-validator-list",
"module-honzon",
"module-idle-scheduler",
"module-incentives",
"module-loans",
"module-nft",
"module-nominees-election",
- "module-polkadot-bridge",
"module-prices",
"module-relaychain",
"module-session-manager",
- "module-staking-pool",
- "module-staking-pool-rpc-runtime-api",
"module-support",
"module-transaction-pause",
"module-transaction-payment",
@@ -5507,14 +5503,27 @@ name = "module-homa"
version = "2.0.1"
dependencies = [
"acala-primitives",
+ "cumulus-primitives-core",
+ "frame-benchmarking",
"frame-support",
"frame-system",
+ "module-currencies",
+ "module-relaychain",
"module-support",
+ "orml-tokens",
+ "orml-traits",
+ "pallet-balances",
+ "pallet-staking",
+ "pallet-xcm",
"parity-scale-codec",
"scale-info",
- "serde",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-io",
"sp-runtime",
"sp-std",
+ "xcm",
+ "xcm-executor",
]
[[package]]
@@ -5791,18 +5800,6 @@ dependencies = [
"sp-std",
]
-[[package]]
-name = "module-staking-pool-rpc-runtime-api"
-version = "2.0.1"
-dependencies = [
- "module-support",
- "parity-scale-codec",
- "serde",
- "sp-api",
- "sp-runtime",
- "sp-std",
-]
-
[[package]]
name = "module-support"
version = "2.0.1"
diff --git a/modules/homa/Cargo.toml b/modules/homa/Cargo.toml
index 356dfeec3a..bd9ddbb940 100644
--- a/modules/homa/Cargo.toml
+++ b/modules/homa/Cargo.toml
@@ -5,27 +5,54 @@ authors = ["Acala Developers"]
edition = "2018"
[dependencies]
-serde = { version = "1.0.124", optional = true }
-codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["max-encoded-len"] }
+codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["derive"] }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
-sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
-sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false, optional = true}
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
-support = { package = "module-support", path = "../support", default-features = false }
+sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+pallet-staking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12", default-features = false }
+xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12", default-features = false }
primitives = { package = "acala-primitives", path = "../../primitives", default-features = false }
+orml-traits = { path = "../../orml/traits", default-features = false }
+module-support = { path = "../../modules/support", default-features = false }
+
+[dev-dependencies]
+sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" }
+pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" }
+module-currencies = { path = "../../modules/currencies" }
+orml-tokens = { path = "../../orml/tokens" }
+xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" }
+cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" }
+module-relaychain = { path = "../relaychain", features = ["kusama"] }
[features]
default = ["std"]
std = [
- "serde",
"codec/std",
+ "frame-benchmarking/std",
+ "frame-support/std",
+ "frame-system/std",
"scale-info/std",
+ "sp-arithmetic/std",
"sp-runtime/std",
+ "sp-core/std",
"sp-std/std",
- "frame-support/std",
- "frame-system/std",
- "support/std",
+ "pallet-staking/std",
+ "pallet-xcm/std",
+ "xcm/std",
"primitives/std",
+ "orml-traits/std",
+ "module-support/std",
+]
+runtime-benchmarks = [
+ "frame-benchmarking",
+ "frame-support/runtime-benchmarks",
+ "frame-system/runtime-benchmarks",
+ "pallet-xcm/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]
diff --git a/modules/homa/src/lib.rs b/modules/homa/src/lib.rs
index 8e763db8c3..d406ce5ba1 100644
--- a/modules/homa/src/lib.rs
+++ b/modules/homa/src/lib.rs
@@ -16,118 +16,962 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-//! # Homa Module
-//!
-//! ## Overview
-//!
-//! The user entrance of Homa protocol. User can inject DOT into the staking
-//! pool and get LDOT, which is the redemption voucher for DOT owned by the
-//! staking pool. The staking pool will staking these DOT to get staking
-//! rewards. Holders of LDOT can choose different ways to redeem DOT.
-
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::unused_unit)]
-use codec::MaxEncodedLen;
-use frame_support::{pallet_prelude::*, transactional};
-use frame_system::pallet_prelude::*;
-use primitives::{Balance, EraIndex};
+use frame_support::{log, pallet_prelude::*, transactional, weights::Weight, BoundedVec, PalletId};
+use frame_system::{ensure_signed, pallet_prelude::*};
+use module_support::{CallBuilder, ExchangeRate, ExchangeRateProvider, Rate};
+use orml_traits::{
+ arithmetic::Signed, BalanceStatus, MultiCurrency, MultiCurrencyExtended, MultiReservableCurrency, XcmTransfer,
+};
+use pallet_staking::EraIndex;
+use primitives::{Balance, CurrencyId};
use scale_info::TypeInfo;
-use sp_runtime::RuntimeDebug;
-use support::HomaProtocol;
-
-pub mod weights;
+use sp_arithmetic::traits::CheckedRem;
+use sp_runtime::{
+ traits::{AccountIdConversion, BlockNumberProvider, Bounded, Convert, One, Saturating, Zero},
+ ArithmeticError, FixedPointNumber, Permill,
+};
+use sp_std::{
+ cmp::{min, Ordering},
+ convert::{From, TryFrom, TryInto},
+ ops::Mul,
+ prelude::*,
+ vec::Vec,
+};
+use xcm::latest::prelude::*;
pub use module::*;
-pub use weights::WeightInfo;
-
-/// Redemption modes:
-/// 1. Immediately: User will immediately get back DOT from the free pool,
-/// which is a liquid pool operated by staking pool, but they have to pay
-/// extra fee.
-/// 2. Target: User can claim the unclaimed unbonding DOT of
-/// specific era, after the remaining unbinding period has passed, users can
-/// get back the DOT.
-/// 3. WaitFor Unbonding: User request unbond, the staking
-/// pool will process unbonding in the next era, and user needs to wait for
-/// the complete unbonding era which determined by Polkadot.
-#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
-pub enum RedeemStrategy {
- Immediately,
- Target(EraIndex),
- WaitForUnbonding,
-}
#[frame_support::pallet]
pub mod module {
use super::*;
+ /// The subaccount's staking ledger which kept by Homa protocol
+ #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Default)]
+ pub struct StakingLedger {
+ /// Corresponding to the active of the subaccount's staking ledger on relaychain
+ #[codec(compact)]
+ pub bonded: Balance,
+ /// Corresponding to the unlocking of the subaccount's staking ledger on relaychain
+ pub unlocking: Vec,
+ }
+
+ /// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked.
+ #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
+ pub struct UnlockChunk {
+ /// Amount of funds to be unlocked.
+ #[codec(compact)]
+ value: Balance,
+ /// Era number at which point it'll be unlocked.
+ #[codec(compact)]
+ era: EraIndex,
+ }
+
+ impl StakingLedger {
+ /// Remove entries from `unlocking` that are sufficiently old and the sum of expired
+ /// unlocking.
+ fn consolidate_unlocked(self, current_era: EraIndex) -> (Self, Balance) {
+ let mut expired_unlocking: Balance = Zero::zero();
+ let unlocking = self
+ .unlocking
+ .into_iter()
+ .filter(|chunk| {
+ if chunk.era > current_era {
+ true
+ } else {
+ expired_unlocking = expired_unlocking.saturating_add(chunk.value);
+ false
+ }
+ })
+ .collect();
+
+ (
+ Self {
+ bonded: self.bonded,
+ unlocking,
+ },
+ expired_unlocking,
+ )
+ }
+ }
+
+ pub type SubAccountIndex = u16;
+ pub(crate) type AmountOf =
+ <::Currency as MultiCurrencyExtended<::AccountId>>::Amount;
+
#[pallet::config]
- pub trait Config: frame_system::Config {
- /// The core of Homa protocol.
- type Homa: HomaProtocol;
+ pub trait Config: frame_system::Config + pallet_xcm::Config {
+ type Event: From> + IsType<::Event>;
+
+ /// Multi-currency support for asset management
+ type Currency: MultiReservableCurrency
+ + MultiCurrencyExtended;
+
+ /// Origin represented Governance
+ type GovernanceOrigin: EnsureOrigin<::Origin>;
+
+ /// The currency id of the Staking asset
+ #[pallet::constant]
+ type StakingCurrencyId: Get;
+
+ /// The currency id of the Liquid asset
+ #[pallet::constant]
+ type LiquidCurrencyId: Get;
+
+ /// The homa's module id.
+ #[pallet::constant]
+ type PalletId: Get;
+
+ /// The default exchange rate for liquid currency to staking currency.
+ #[pallet::constant]
+ type DefaultExchangeRate: Get;
+
+ /// The threshold for mint operation in staking currency.
+ #[pallet::constant]
+ type MintThreshold: Get;
+
+ /// The threshold for redeem operation in liquid currency.
+ #[pallet::constant]
+ type RedeemThreshold: Get;
+
+ /// The account of parachain on the relaychain.
+ #[pallet::constant]
+ type ParachainAccount: Get;
+
+ /// The index list of active Homa subaccounts.
+ /// `active` means these subaccounts can continue do bond/unbond operations by Homa.
+ #[pallet::constant]
+ type ActiveSubAccountsIndexList: Get>;
+
+ /// The bonded soft cap for each subaccount, use len(ActiveSubAccountsIndexList) *
+ /// SoftBondedCapPerSubAccount as the staking currency cap.
+ #[pallet::constant]
+ type SoftBondedCapPerSubAccount: Get;
+
+ /// The keepers list which are allowed to do fast match redeem request.
+ #[pallet::constant]
+ type FastMatchKeepers: Get>;
- /// Weight information for the extrinsics in this module.
- type WeightInfo: WeightInfo;
+ /// Number of eras for unbonding is expired on relaychain.
+ #[pallet::constant]
+ type BondingDuration: Get;
+
+ /// Unbonding slashing spans for unbonding on the relaychain.
+ #[pallet::constant]
+ type RelayChainUnbondingSlashingSpans: Get;
+
+ /// The estimated staking reward rate per era on relaychain.
+ #[pallet::constant]
+ type EstimatedRewardRatePerEra: Get;
+
+ /// The fixed staking currency cost of transaction fee for XCMTransfer.
+ #[pallet::constant]
+ type XcmTransferFee: Get;
+
+ /// The fixed staking currency cost of extra fee for xcm message
+ #[pallet::constant]
+ type XcmMessageFee: Get;
+
+ /// The Call builder for communicating with RelayChain via XCM messaging.
+ type RelayChainCallBuilder: CallBuilder;
+
+ /// The interface to Cross-chain transfer.
+ type XcmTransfer: XcmTransfer;
+
+ /// The convert for convert sovereign subacocunt index to the MultiLocation where the
+ /// staking currencies are sent to.
+ type SovereignSubAccountLocationConvert: Convert;
+ }
+
+ #[pallet::error]
+ pub enum Error {
+ /// The mint amount is below the threshold.
+ BelowMintThreshold,
+ /// The redeem amount to request is below the threshold.
+ BelowRedeemThreshold,
+ /// The caller is not in `FastMatchKeepers` list.
+ NotAllowedKeeper,
+ /// The mint will cause staking currency of Homa exceed the soft cap.
+ ExceededStakingCurrencySoftCap,
+ /// UnclaimedRedemption is not enough, this error is not expected.
+ InsufficientUnclaimedRedemption,
+ /// Invalid era index to bump, must be greater than RelayChainCurrentEra
+ InvalidEraIndex,
+ /// The xcm operation have failed
+ XcmFailed,
}
+ #[pallet::event]
+ #[pallet::generate_deposit(pub(crate) fn deposit_event)]
+ pub enum Event {
+ /// The minter use staking currency to mint liquid currency. \[minter,
+ /// staking_currency_amount, liquid_currency_amount_received\]
+ Minted(T::AccountId, Balance, Balance),
+ /// Request redeem. \[redeemer, liquid_amount, fast_match_fee_rate\]
+ RequestedRedeem(T::AccountId, Balance, Rate),
+ /// Redeem request has been cancelled. \[redeemer, cancelled_liquid_amount\]
+ RedeemRequestCancelled(T::AccountId, Balance),
+ /// Redeem request is redeemed partially or fully by fast match. \[redeemer,
+ /// matched_liquid_amount, fee_in_liquid, redeemed_staking_amount\]
+ RedeemedByFastMatch(T::AccountId, Balance, Balance, Balance),
+ /// The redeemer withdraw expired redemption. \[redeemer, redeption_amount\]
+ WithdrawRedemption(T::AccountId, Balance),
+ /// The redeemer withdraw expired redemption. \[redeemer, redeption_amount\]
+ XcmDestWeightUpdated(Weight),
+ /// The current era has been bumped. \[new_era_index\]
+ CurrentEraBumped(EraIndex),
+ /// The bonded amount of subaccount's ledger has been updated. \[sub_account_index,
+ /// new_bonded_amount\]
+ LedgerBondedUpdated(SubAccountIndex, Balance),
+ /// The unlocking of subaccount's ledger has been updated. \[sub_account_index,
+ /// new_unlocking\]
+ LedgerUnlockingUpdated(SubAccountIndex, Vec),
+ }
+
+ /// The current era of relaychain
+ ///
+ /// RelayChainCurrentEra : EraIndex
+ #[pallet::storage]
+ #[pallet::getter(fn relay_chain_current_era)]
+ pub type RelayChainCurrentEra = StorageValue<_, EraIndex, ValueQuery>;
+
+ // /// The latest processed era on by, it should be always <= RelayChainCurrentEra
+ // ///
+ // /// ProcessedEra : EraIndex
+ // #[pallet::storage]
+ // #[pallet::getter(fn processed_era)]
+ // pub type ProcessedEra = StorageValue<_, EraIndex, ValueQuery>;
+
+ /// The staking ledger of Homa subaccounts.
+ ///
+ /// StakingLedgers map: SubAccountIndex => Option
+ #[pallet::storage]
+ #[pallet::getter(fn staking_ledgers)]
+ pub type StakingLedgers = StorageMap<_, Twox64Concat, SubAccountIndex, StakingLedger, OptionQuery>;
+
+ /// The total staking currency to bond on relaychain when new era,
+ /// and that is available to be match fast redeem request.
+ /// ToBondPool value: StakingCurrencyAmount
+ #[pallet::storage]
+ #[pallet::getter(fn to_bond_pool)]
+ pub type ToBondPool = StorageValue<_, Balance, ValueQuery>;
+
+ /// The total amount of void liquid currency. It's will not be issued,
+ /// used to avoid newly issued LDOT to obtain the incoming staking income from relaychain.
+ /// And it is guaranteed that the current exchange rate between liquid currency and staking
+ /// currency will not change. It will be reset to 0 at the end of the rebalance when new era.
+ ///
+ /// TotalVoidLiquid value: LiquidCurrencyAmount
+ #[pallet::storage]
+ #[pallet::getter(fn total_void_liquid)]
+ pub type TotalVoidLiquid = StorageValue<_, Balance, ValueQuery>;
+
+ /// The total unclaimed redemption.
+ ///
+ /// UnclaimedRedemption value: StakingCurrencyAmount
+ #[pallet::storage]
+ #[pallet::getter(fn unclaimed_redemption)]
+ pub type UnclaimedRedemption = StorageValue<_, Balance, ValueQuery>;
+
+ /// Requests to redeem staked currencies.
+ /// RedeemRequests: Map: AccountId => Option<(liquid_amount: Balance, addtional_fee: Rate)>
+ #[pallet::storage]
+ #[pallet::getter(fn redeem_requests)]
+ pub type RedeemRequests = StorageMap<_, Twox64Concat, T::AccountId, (Balance, Rate), OptionQuery>;
+
+ /// The records of unbonding by AccountId.
+ ///
+ /// Unbondings: double_map AccountId, ExpireEraIndex => UnboundingStakingCurrencyAmount
+ #[pallet::storage]
+ #[pallet::getter(fn unbondings)]
+ pub type Unbondings =
+ StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, EraIndex, Balance, ValueQuery>;
+
+ /// The weight for excution XCM msg on relaychain. Must be higher than
+ /// T::BaseXcmWeight + T::Weigher::weight(xcm_message_sended_by_homa)` on relaychain.
+ ///
+ /// xcm_dest_weight: value: Weight
+ #[pallet::storage]
+ #[pallet::getter(fn xcm_dest_weight)]
+ pub type XcmDestWeight = StorageValue<_, Weight, ValueQuery>;
+
#[pallet::pallet]
pub struct Pallet(_);
#[pallet::hooks]
- impl Hooks for Pallet {}
+ impl Hooks for Pallet {
+ fn integrity_test() {
+ assert!(!T::DefaultExchangeRate::get().is_zero());
+ }
+ }
#[pallet::call]
impl Pallet {
- /// Inject DOT to staking pool and mint LDOT in a certain exchange rate
- /// decided by staking pool.
+ /// Mint liquid currency by put locking up amount of staking currency.
///
- /// - `amount`: the DOT amount to inject into staking pool.
- #[pallet::weight(::WeightInfo::mint())]
+ /// Parameters:
+ /// - `amount`: The amount of staking currency used to mint liquid currency.
+ #[pallet::weight(10_000)]
#[transactional]
pub fn mint(origin: OriginFor, #[pallet::compact] amount: Balance) -> DispatchResult {
- let who = ensure_signed(origin)?;
- T::Homa::mint(&who, amount)?;
+ let minter = ensure_signed(origin)?;
+
+ // Ensure the amount is above the mint threshold.
+ ensure!(amount > T::MintThreshold::get(), Error::::BelowMintThreshold);
+
+ // TODO: is that neccesary?
+ ensure!(
+ Self::get_total_staking_currency().saturating_add(amount) <= Self::get_staking_currency_soft_cap(),
+ Error::::ExceededStakingCurrencySoftCap
+ );
+
+ T::Currency::transfer(T::StakingCurrencyId::get(), &minter, &Self::account_id(), amount)?;
+ ToBondPool::::mutate(|pool| *pool = pool.saturating_add(amount));
+
+ // calculate the liquid amount by the current exchange rate.
+ let liquid_amount = Self::convert_staking_to_liquid(amount)?;
+ let liquid_issue_to_minter = Rate::one()
+ .saturating_add(T::EstimatedRewardRatePerEra::get())
+ .reciprocal()
+ .expect("shouldn't be invalid!")
+ .saturating_mul_int(liquid_amount);
+ let liquid_add_to_void = liquid_amount.saturating_sub(liquid_issue_to_minter);
+
+ T::Currency::deposit(T::LiquidCurrencyId::get(), &minter, liquid_issue_to_minter)?;
+ TotalVoidLiquid::::mutate(|total| *total = total.saturating_add(liquid_add_to_void));
+
+ Self::deposit_event(Event::::Minted(minter, amount, liquid_issue_to_minter));
Ok(())
}
- /// Burn LDOT and redeem DOT from staking pool.
+ /// Build/Cancel/Overwrite a redeem request, use liquid currency to redeem staking currency.
+ /// The redeem request will be executed in two ways:
+ /// 1. Redeem by fast match: Homa use staking currency in ToBondPool to match redeem request
+ /// in the current era, setting a higher fee_rate can increase the possibility of being fast
+ /// matched. 2. Redeem by unbond on relaychain: if redeem request has not been fast matched
+ /// in current era, Homa will unbond staking currency on relaychain when the next era
+ /// bumped. So redeemer at least wait for the unbonding period + extra 1 era to get the
+ /// redemption.
///
- /// - `amount`: the LDOT amount to redeem.
- /// - `strategy`: redemption mode.
- #[pallet::weight(match *strategy {
- RedeemStrategy::Immediately => ::WeightInfo::redeem_immediately(),
- RedeemStrategy::Target(_) => ::WeightInfo::redeem_by_claim_unbonding(),
- RedeemStrategy::WaitForUnbonding => ::WeightInfo::redeem_wait_for_unbonding(),
- })]
+ /// Parameters:
+ /// - `amount`: The amount of liquid currency to be requested redeemed into Staking
+ /// currency.
+ /// - `fast_match_fee_rate`: Fee rate for pay fast match.
+ #[pallet::weight(10_000)]
#[transactional]
- pub fn redeem(
+ pub fn request_redeem(
origin: OriginFor,
#[pallet::compact] amount: Balance,
- strategy: RedeemStrategy,
+ fast_match_fee_rate: Rate,
) -> DispatchResult {
+ let redeemer = ensure_signed(origin)?;
+
+ RedeemRequests::::try_mutate_exists(&redeemer, |maybe_request| -> DispatchResult {
+ let (previous_request_amount, _) = maybe_request.take().unwrap_or_default();
+ let liquid_currency_id = T::LiquidCurrencyId::get();
+
+ ensure!(
+ (!previous_request_amount.is_zero() && amount.is_zero()) || amount >= T::RedeemThreshold::get(),
+ Error::::BelowRedeemThreshold
+ );
+
+ match amount.cmp(&previous_request_amount) {
+ Ordering::Greater =>
+ // pay more liquid currency.
+ {
+ T::Currency::transfer(
+ liquid_currency_id,
+ &redeemer,
+ &Self::account_id(),
+ amount.saturating_sub(previous_request_amount),
+ )
+ }
+ Ordering::Less =>
+ // refund the difference.
+ {
+ T::Currency::transfer(
+ liquid_currency_id,
+ &Self::account_id(),
+ &redeemer,
+ previous_request_amount.saturating_sub(amount),
+ )
+ }
+ _ => Ok(()),
+ }?;
+
+ if !amount.is_zero() {
+ *maybe_request = Some((amount, fast_match_fee_rate));
+ Self::deposit_event(Event::::RequestedRedeem(
+ redeemer.clone(),
+ amount,
+ fast_match_fee_rate,
+ ));
+ } else {
+ if !previous_request_amount.is_zero() {
+ Self::deposit_event(Event::::RedeemRequestCancelled(
+ redeemer.clone(),
+ previous_request_amount,
+ ));
+ }
+ }
+ Ok(())
+ })
+ }
+
+ /// Execute fast match for specific redeem requests.
+ /// Caller must be in `FastMatchKeepers` list.
+ ///
+ /// Parameters:
+ /// - `redeemer_list`: The list of redeem requests to execute fast redeem.
+ #[pallet::weight(10_000)]
+ #[transactional]
+ pub fn fast_match_redeems(origin: OriginFor, redeemer_list: Vec) -> DispatchResult {
let who = ensure_signed(origin)?;
- match strategy {
- RedeemStrategy::Immediately => {
- T::Homa::redeem_by_free_unbonded(&who, amount)?;
+ ensure!(T::FastMatchKeepers::get().contains(&who), Error::::NotAllowedKeeper);
+
+ for redeemer in redeemer_list {
+ Self::do_fast_match_redeem(&redeemer)?;
+ }
+ Ok(())
+ }
+
+ /// Withdraw the expired redemption of specific redeemer by unbond.
+ ///
+ /// Parameters:
+ /// - `redeemer`: redeemer.
+ #[pallet::weight(10_000)]
+ #[transactional]
+ pub fn claim_redemption(origin: OriginFor, redeemer: T::AccountId) -> DispatchResult {
+ let _ = ensure_signed(origin)?;
+
+ let mut available_staking: Balance = Zero::zero();
+ Unbondings::::iter_prefix(&redeemer)
+ .filter(|(era_index, _)| era_index <= &Self::relay_chain_current_era())
+ .for_each(|(expired_era_index, unbonded)| {
+ available_staking = available_staking.saturating_add(available_staking);
+ Unbondings::::remove(&redeemer, expired_era_index);
+ });
+ UnclaimedRedemption::::try_mutate(|total| -> DispatchResult {
+ *total = total
+ .checked_sub(available_staking)
+ .ok_or(Error::::InsufficientUnclaimedRedemption)?;
+ Ok(())
+ });
+ T::Currency::transfer(
+ T::StakingCurrencyId::get(),
+ &Self::account_id(),
+ &redeemer,
+ available_staking,
+ )?;
+
+ Self::deposit_event(Event::::WithdrawRedemption(redeemer, available_staking));
+ Ok(())
+ }
+
+ /// Bump the current era to keep consistent with relaychain.
+ /// Requires `GovernanceOrigin`
+ ///
+ /// Parameters:
+ /// - `new_era`: the latest era index of relaychain.
+ #[pallet::weight(10_000)]
+ #[transactional]
+ pub fn bump_current_era(origin: OriginFor, new_era: EraIndex) -> DispatchResult {
+ T::GovernanceOrigin::ensure_origin(origin)?;
+
+ RelayChainCurrentEra::::try_mutate(|current_era| -> DispatchResult {
+ ensure!(new_era > *current_era, Error::::InvalidEraIndex);
+ *current_era = new_era;
+
+ // reset void liquid to zero
+ TotalVoidLiquid::::put(0);
+
+ // TODO: consider execute rebalance on on_idle, tut before the processing is completed,
+ // the mint and request_redeem functions should be unavailable.
+ // Rebalance:
+ Self::process_scheduled_unbond(new_era)?;
+ Self::process_to_bond_pool(new_era)?;
+ Self::process_redeem_requests(new_era)?;
+
+ Self::deposit_event(Event::::CurrentEraBumped(new_era));
+ Ok(())
+ })
+ }
+
+ /// Update the bonded and unbonding to local subaccounts ledger according to the ledger on
+ /// relaychain. Requires `GovernanceOrigin`
+ ///
+ /// Parameters:
+ /// - `new_era`: the latest era index of relaychain.
+ #[pallet::weight(10_000)]
+ #[transactional]
+ pub fn update_ledgers(
+ origin: OriginFor,
+ updates: Vec<(SubAccountIndex, Option, Option>)>,
+ ) -> DispatchResult {
+ T::GovernanceOrigin::ensure_origin(origin)?;
+
+ for (sub_account_index, bonded_change, unlocking_change) in updates {
+ Self::do_update_ledger(sub_account_index, |ledger| -> DispatchResult {
+ if let Some(bonded) = bonded_change {
+ ledger.bonded = bonded;
+ }
+ if let Some(unlocking) = unlocking_change {
+ ledger.unlocking = unlocking;
+ }
+ Ok(())
+ })?;
+ }
+
+ Ok(())
+ }
+
+ /// Sets the xcm_dest_weight for XCM staking operations.
+ /// Requires `GovernanceOrigin`
+ ///
+ /// Parameters:
+ /// - `xcm_dest_weight`: The new weight for XCM staking operations.
+ #[pallet::weight(10_000)]
+ #[transactional]
+ pub fn update_xcm_dest_weight(
+ origin: OriginFor,
+ #[pallet::compact] xcm_dest_weight: Weight,
+ ) -> DispatchResult {
+ T::GovernanceOrigin::ensure_origin(origin)?;
+
+ XcmDestWeight::::put(xcm_dest_weight);
+ Self::deposit_event(Event::::XcmDestWeightUpdated(xcm_dest_weight));
+ Ok(())
+ }
+ }
+
+ impl Pallet {
+ /// Module account id
+ pub fn account_id() -> T::AccountId {
+ T::PalletId::get().into_account()
+ }
+
+ fn do_update_ledger(
+ sub_account_index: SubAccountIndex,
+ f: impl FnOnce(&mut StakingLedger) -> sp_std::result::Result,
+ ) -> sp_std::result::Result {
+ StakingLedgers::::try_mutate_exists(sub_account_index, |maybe_ledger| {
+ let mut ledger = maybe_ledger.take().unwrap_or_default();
+ let old_ledger = ledger.clone();
+
+ f(&mut ledger).map(move |result| {
+ if ledger.bonded != old_ledger.bonded {
+ Self::deposit_event(Event::::LedgerBondedUpdated(sub_account_index, ledger.bonded));
+ }
+ if ledger.unlocking != old_ledger.unlocking {
+ Self::deposit_event(Event::::LedgerUnlockingUpdated(
+ sub_account_index,
+ ledger.unlocking.clone(),
+ ));
+ }
+
+ *maybe_ledger = if ledger == Default::default() {
+ None
+ } else {
+ Some(ledger)
+ };
+
+ result
+ })
+ })
+ }
+
+ /// Get the soft cap of total staking currency of Homa.
+ /// Soft cap = ActiveSubAccountsIndexList.len() * SoftBondedCapPerSubAccount
+ pub fn get_staking_currency_soft_cap() -> Balance {
+ T::SoftBondedCapPerSubAccount::get().saturating_mul(T::ActiveSubAccountsIndexList::get().len() as Balance)
+ }
+
+ /// Calculate the total amount of staking currency belong to Homa.
+ pub fn get_total_staking_currency() -> Balance {
+ let total_bonded: Balance = StakingLedgers::::iter().fold(Zero::zero(), |total_bonded, (_, ledger)| {
+ total_bonded.saturating_add(ledger.bonded)
+ });
+ let to_bond_pool = Self::to_bond_pool();
+ total_bonded.saturating_add(to_bond_pool)
+ }
+
+ /// Calculate the current exchange rate between the staking currency and liquid currency.
+ /// Note: ExchangeRate(staking : liquid) = total_staking_amount / (liquid_total_issuance +
+ /// total_void_liquid) If the exchange rate cannot be calculated, T::DefaultExchangeRate is
+ /// used.
+ pub fn current_exchange_rate() -> ExchangeRate {
+ let total_staking = Self::get_total_staking_currency();
+ let total_liquid =
+ T::Currency::total_issuance(T::LiquidCurrencyId::get()).saturating_add(Self::total_void_liquid());
+ if total_staking.is_zero() {
+ T::DefaultExchangeRate::get()
+ } else {
+ ExchangeRate::checked_from_rational(total_staking, total_liquid)
+ .unwrap_or_else(T::DefaultExchangeRate::get)
+ }
+ }
+
+ /// Calculate the amount of staking currency converted from liquid currency by current
+ /// exchange rate.
+ pub fn convert_liquid_to_staking(liquid_amount: Balance) -> Result {
+ Self::current_exchange_rate()
+ .checked_mul_int(liquid_amount)
+ .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))
+ }
+
+ /// Calculate the amount of liquid currency converted from staking currency by current
+ /// exchange rate.
+ pub fn convert_staking_to_liquid(staking_amount: Balance) -> Result {
+ Self::current_exchange_rate()
+ .reciprocal()
+ .unwrap_or_else(|| T::DefaultExchangeRate::get().reciprocal().unwrap())
+ .checked_mul_int(staking_amount)
+ .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))
+ }
+
+ #[transactional]
+ pub fn do_fast_match_redeem(redeemer: &T::AccountId) -> DispatchResult {
+ RedeemRequests::::try_mutate_exists(redeemer, |maybe_request| -> DispatchResult {
+ if let Some((request_amount, fee_rate)) = maybe_request.take() {
+ // calculate the liquid currency limit can be used to redeem based on ToBondPool at fee_rate.
+ let available_staking_currency = Self::to_bond_pool();
+ let liquid_currency_limit = Self::convert_staking_to_liquid(available_staking_currency)?;
+ let liquid_limit_at_fee_rate = Rate::one()
+ .saturating_sub(fee_rate)
+ .reciprocal()
+ .unwrap_or_else(Bounded::max_value)
+ .saturating_mul_int(liquid_currency_limit);
+ let module_account = Self::account_id();
+
+ // calculate the acutal liquid currency to be used to redeem
+ let actual_liquid_to_redeem = if liquid_limit_at_fee_rate >= request_amount {
+ request_amount
+ } else {
+ // if cannot fast match the request amount fully, at least keep RedeemThreshold as remainer.
+ liquid_limit_at_fee_rate.min(request_amount.saturating_sub(T::RedeemThreshold::get()))
+ };
+
+ if !actual_liquid_to_redeem.is_zero() {
+ let liquid_to_burn = Rate::one()
+ .saturating_sub(fee_rate)
+ .saturating_mul_int(actual_liquid_to_redeem);
+ let redeemed_staking = Self::convert_liquid_to_staking(liquid_to_burn)?;
+ let fee_in_liquid = actual_liquid_to_redeem.saturating_sub(liquid_to_burn);
+
+ // TODO: record the fee_in_liquid reward it to HomaTreasury as benifit.
+
+ // burn liquid_to_burn
+ T::Currency::withdraw(T::LiquidCurrencyId::get(), &module_account, liquid_to_burn)?;
+
+ // transfer redeemed_staking to redeemer.
+ T::Currency::transfer(
+ T::StakingCurrencyId::get(),
+ &module_account,
+ redeemer,
+ redeemed_staking,
+ )?;
+ ToBondPool::::mutate(|pool| *pool = pool.saturating_sub(redeemed_staking));
+
+ Self::deposit_event(Event::::RedeemedByFastMatch(
+ redeemer.clone(),
+ actual_liquid_to_redeem,
+ fee_in_liquid,
+ redeemed_staking,
+ ));
+ }
+
+ // update request amount
+ let remainer_request_amount = request_amount.saturating_sub(actual_liquid_to_redeem);
+ if !remainer_request_amount.is_zero() {
+ *maybe_request = Some((remainer_request_amount, fee_rate));
+ }
}
- RedeemStrategy::Target(target_era) => {
- T::Homa::redeem_by_claim_unbonding(&who, amount, target_era)?;
+
+ Ok(())
+ })
+ }
+
+ /// Get back unbonded of all subaccounts on relaychain by XCM.
+ /// The staking currency withdrew becomes available to be redeemed.
+ #[transactional]
+ pub fn process_scheduled_unbond(new_era: EraIndex) -> DispatchResult {
+ log::debug!(
+ target: "homa",
+ "process scheduled unbond on era: {:?}",
+ new_era
+ );
+
+ let mut total_withdrawn_staking: Balance = Zero::zero();
+
+ // iterate all subaccounts
+ for (sub_account_index, ledger) in StakingLedgers::::iter() {
+ let (new_ledger, expired_unlocking) = ledger.consolidate_unlocked(new_era);
+
+ if !expired_unlocking.is_zero() {
+ Self::withdraw_unbonded_from_relaychain(sub_account_index, expired_unlocking)?;
+
+ // udpate ledger
+ Self::do_update_ledger(sub_account_index, |before| -> DispatchResult {
+ *before = new_ledger;
+ Ok(())
+ })?;
+ total_withdrawn_staking = total_withdrawn_staking.saturating_add(expired_unlocking);
}
- RedeemStrategy::WaitForUnbonding => {
- T::Homa::redeem_by_unbond(&who, amount)?;
+ }
+
+ // issue withdrawn unbonded to module account for redeemer to claim
+ T::Currency::deposit(
+ T::StakingCurrencyId::get(),
+ &Self::account_id(),
+ total_withdrawn_staking,
+ )?;
+ UnclaimedRedemption::::mutate(|total| *total = total.saturating_add(total_withdrawn_staking));
+
+ Ok(())
+ }
+
+ /// Distribute PoolToBond to ActiveSubAccountsIndexList, then cross-transfer the
+ /// distribution amount to the subaccounts on relaychain and bond it by XCM.
+ #[transactional]
+ pub fn process_to_bond_pool(new_era: EraIndex) -> DispatchResult {
+ log::debug!(
+ target: "homa",
+ "process to bond pool on era: {:?}",
+ new_era
+ );
+
+ let xcm_transfer_fee = T::XcmTransferFee::get();
+ let bonded_list: Vec<(SubAccountIndex, Balance)> = T::ActiveSubAccountsIndexList::get()
+ .iter()
+ .map(|index| (*index, Self::staking_ledgers(index).unwrap_or_default().bonded))
+ .collect();
+ let (distribution, remainer) = distribute_increment::(
+ bonded_list,
+ Self::to_bond_pool(),
+ Some(T::SoftBondedCapPerSubAccount::get()),
+ Some(xcm_transfer_fee),
+ );
+
+ // subaccounts execute the distribution
+ for (sub_account_index, amount) in distribution {
+ if !amount.is_zero() {
+ Self::transfer_and_bond_to_relaychain(sub_account_index, amount)?;
+
+ // udpate ledger
+ Self::do_update_ledger(sub_account_index, |ledger| -> DispatchResult {
+ ledger.bonded = ledger.bonded.saturating_add(amount.saturating_sub(xcm_transfer_fee));
+ Ok(())
+ })?;
}
}
+
+ // update pool
+ ToBondPool::::mutate(|pool| *pool = remainer);
Ok(())
}
- /// Get back those DOT that have been unbonded.
- #[pallet::weight(::WeightInfo::withdraw_redemption())]
+ /// Process redeem requests and subaccounts do unbond on relaychain by XCM message.
#[transactional]
- pub fn withdraw_redemption(origin: OriginFor) -> DispatchResult {
- let who = ensure_signed(origin)?;
- T::Homa::withdraw_redemption(&who)?;
+ pub fn process_redeem_requests(new_era: EraIndex) -> DispatchResult {
+ log::debug!(
+ target: "homa",
+ "process redeem requests on era: {:?}",
+ new_era
+ );
+
+ let mut total_redeem_amount: Balance = Zero::zero();
+ let era_index_to_expire = new_era + T::BondingDuration::get();
+
+ // drain RedeemRequests and insert to Unbondings
+ for (redeemer, (redeem_amount, _)) in RedeemRequests::::drain() {
+ total_redeem_amount = total_redeem_amount.saturating_add(redeem_amount);
+ let redemption_amount = Self::convert_liquid_to_staking(redeem_amount)?;
+ Unbondings::::insert(redeemer, era_index_to_expire, redemption_amount);
+ }
+
+ // calculate the distribution for unbond
+ let staking_amount_to_unbond = Self::convert_liquid_to_staking(total_redeem_amount)?;
+ let bonded_list: Vec<(SubAccountIndex, Balance)> = T::ActiveSubAccountsIndexList::get()
+ .iter()
+ .map(|index| (*index, Self::staking_ledgers(index).unwrap_or_default().bonded))
+ .collect();
+ let (distribution, _) =
+ distribute_decrement::(bonded_list, staking_amount_to_unbond, None, None);
+
+ // subaccounts execute the distribution
+ for (sub_account_index, unbond_amount) in distribution {
+ if !unbond_amount.is_zero() {
+ Self::unbond_on_relaychain(sub_account_index, unbond_amount)?;
+
+ // udpate ledger
+ Self::do_update_ledger(sub_account_index, |ledger| -> DispatchResult {
+ ledger.bonded = ledger.bonded.saturating_sub(unbond_amount);
+ ledger.unlocking.push(UnlockChunk {
+ value: unbond_amount,
+ era: era_index_to_expire,
+ });
+ Ok(())
+ })?;
+ }
+ }
+
+ // burn total_redeem_amount.
+ T::Currency::withdraw(T::LiquidCurrencyId::get(), &Self::account_id(), total_redeem_amount)?;
+
+ Ok(())
+ }
+
+ /// Send XCM message to the relaychain to withdraw_unbonded staking currency from
+ /// subaccount.
+ pub fn withdraw_unbonded_from_relaychain(
+ sub_account_index: SubAccountIndex,
+ amount: Balance,
+ ) -> DispatchResult {
+ let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message(
+ T::RelayChainCallBuilder::utility_as_derivative_call(
+ T::RelayChainCallBuilder::utility_batch_call(vec![
+ T::RelayChainCallBuilder::staking_withdraw_unbonded(T::RelayChainUnbondingSlashingSpans::get()),
+ T::RelayChainCallBuilder::balances_transfer_keep_alive(T::ParachainAccount::get(), amount),
+ ]),
+ sub_account_index,
+ ),
+ T::XcmMessageFee::get(),
+ Self::xcm_dest_weight(),
+ );
+
+ let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message);
+ log::debug!(
+ target: "homa",
+ "subaccount {:?} send XCM to withdraw unbonded {:?} on relaychain result: {:?}",
+ sub_account_index, amount, result
+ );
+ ensure!(result.is_ok(), Error::::XcmFailed);
+ Ok(())
+ }
+
+ /// Cross-chain transfer staking currency to subaccount and bond it on relaychain by XCM
+ /// message.
+ pub fn transfer_and_bond_to_relaychain(sub_account_index: SubAccountIndex, amount: Balance) -> DispatchResult {
+ T::XcmTransfer::transfer(
+ Self::account_id(),
+ T::StakingCurrencyId::get(),
+ amount,
+ T::SovereignSubAccountLocationConvert::convert(sub_account_index),
+ Self::xcm_dest_weight(),
+ )?;
+
+ let bond_amount = amount.saturating_sub(T::XcmTransferFee::get());
+ let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message(
+ T::RelayChainCallBuilder::utility_as_derivative_call(
+ T::RelayChainCallBuilder::staking_bond_extra(bond_amount),
+ sub_account_index,
+ ),
+ T::XcmMessageFee::get(),
+ Self::xcm_dest_weight(),
+ );
+ let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message);
+ log::debug!(
+ target: "homa",
+ "subaccount {:?} send XCM to bond {:?} on relaychain result: {:?}",
+ sub_account_index, bond_amount, result,
+ );
+ ensure!(result.is_ok(), Error::::XcmFailed);
Ok(())
}
+
+ /// Send XCM message to the relaychain to unbond subaccount.
+ pub fn unbond_on_relaychain(sub_account_index: SubAccountIndex, amount: Balance) -> DispatchResult {
+ let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message(
+ T::RelayChainCallBuilder::utility_as_derivative_call(
+ T::RelayChainCallBuilder::staking_unbond(amount),
+ sub_account_index,
+ ),
+ T::XcmMessageFee::get(),
+ Self::xcm_dest_weight(),
+ );
+ let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message);
+ log::debug!(
+ target: "homa",
+ "subaccount {:?} send XCM to unbond {:?} on relaychain result: {:?}",
+ sub_account_index, amount, result
+ );
+ ensure!(result.is_ok(), Error::::XcmFailed);
+ Ok(())
+ }
+ }
+
+ impl ExchangeRateProvider for Pallet {
+ fn get_exchange_rate() -> ExchangeRate {
+ Self::current_exchange_rate()
+ }
}
}
+
+/// Helpers for distribute increment/decrement to as possible to keep the list balanced after
+/// distribution.
+pub fn distribute_increment(
+ mut amount_list: Vec<(Index, Balance)>,
+ total_increment: Balance,
+ amount_cap: Option,
+ minimum_increment: Option,
+) -> (Vec<(Index, Balance)>, Balance) {
+ let mut remain_increment = total_increment;
+ let mut distribution_list: Vec<(Index, Balance)> = vec![];
+
+ // Sort by amount in ascending order
+ amount_list.sort_by(|a, b| a.1.cmp(&b.1));
+
+ for (index, amount) in amount_list {
+ if remain_increment.is_zero() || remain_increment < minimum_increment.unwrap_or_else(Bounded::max_value) {
+ break;
+ }
+
+ let increment_distribution = amount_cap
+ .unwrap_or_else(Bounded::max_value)
+ .saturating_add(amount)
+ .min(remain_increment);
+ if increment_distribution.is_zero()
+ || increment_distribution < minimum_increment.unwrap_or_else(Bounded::max_value)
+ {
+ continue;
+ }
+ distribution_list.push((index, increment_distribution));
+ remain_increment = remain_increment.saturating_sub(increment_distribution);
+ }
+
+ (distribution_list, remain_increment)
+}
+
+pub fn distribute_decrement(
+ mut amount_list: Vec<(Index, Balance)>,
+ total_decrement: Balance,
+ amount_remainer: Option,
+ minimum_decrement: Option,
+) -> (Vec<(Index, Balance)>, Balance) {
+ let mut remain_decrement = total_decrement;
+ let mut distribution_list: Vec<(Index, Balance)> = vec![];
+
+ // Sort by amount in descending order
+ amount_list.sort_by(|a, b| b.1.cmp(&a.1));
+
+ for (index, amount) in amount_list {
+ if remain_decrement.is_zero() || remain_decrement < minimum_decrement.unwrap_or_else(Bounded::max_value) {
+ break;
+ }
+
+ let decrement_distribution = amount
+ .saturating_sub(amount_remainer.unwrap_or_else(Bounded::min_value))
+ .min(remain_decrement);
+ if decrement_distribution.is_zero()
+ || decrement_distribution < minimum_decrement.unwrap_or_else(Bounded::min_value)
+ {
+ continue;
+ }
+ distribution_list.push((index, decrement_distribution));
+ remain_decrement = remain_decrement.saturating_sub(decrement_distribution);
+ }
+
+ (distribution_list, remain_decrement)
+}
diff --git a/modules/homa/src/weights.rs b/modules/homa/src/weights.rs
deleted file mode 100644
index 2e2ed72e25..0000000000
--- a/modules/homa/src/weights.rs
+++ /dev/null
@@ -1,115 +0,0 @@
-// This file is part of Acala.
-
-// Copyright (C) 2020-2021 Acala Foundation.
-// 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_homa
-//!
-//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0
-//! DATE: 2021-02-26, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: []
-//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
-
-// Executed Command:
-// target/release/acala
-// benchmark
-// --chain=dev
-// --steps=50
-// --repeat=20
-// --pallet=module_homa
-// --extrinsic=*
-// --execution=wasm
-// --wasm-execution=compiled
-// --heap-pages=4096
-// --output=./modules/homa/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_homa.
-pub trait WeightInfo {
- fn mint() -> Weight;
- fn redeem_immediately() -> Weight;
- fn redeem_wait_for_unbonding() -> Weight;
- fn redeem_by_claim_unbonding() -> Weight;
- fn withdraw_redemption() -> Weight;
-}
-
-/// Weights for module_homa using the Acala node and recommended hardware.
-pub struct AcalaWeight(PhantomData);
-impl WeightInfo for AcalaWeight {
- fn mint() -> Weight {
- (100_000_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(8 as Weight))
- .saturating_add(T::DbWeight::get().writes(8 as Weight))
- }
- fn redeem_immediately() -> Weight {
- (115_000_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(6 as Weight))
- .saturating_add(T::DbWeight::get().writes(5 as Weight))
- }
- fn redeem_wait_for_unbonding() -> Weight {
- (59_000_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(4 as Weight))
- .saturating_add(T::DbWeight::get().writes(4 as Weight))
- }
- fn redeem_by_claim_unbonding() -> Weight {
- (89_000_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(7 as Weight))
- .saturating_add(T::DbWeight::get().writes(5 as Weight))
- }
- fn withdraw_redemption() -> Weight {
- (64_000_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(5 as Weight))
- .saturating_add(T::DbWeight::get().writes(3 as Weight))
- }
-}
-
-// For backwards compatibility and tests
-impl WeightInfo for () {
- fn mint() -> Weight {
- (100_000_000 as Weight)
- .saturating_add(RocksDbWeight::get().reads(8 as Weight))
- .saturating_add(RocksDbWeight::get().writes(8 as Weight))
- }
- fn redeem_immediately() -> Weight {
- (115_000_000 as Weight)
- .saturating_add(RocksDbWeight::get().reads(6 as Weight))
- .saturating_add(RocksDbWeight::get().writes(5 as Weight))
- }
- fn redeem_wait_for_unbonding() -> Weight {
- (59_000_000 as Weight)
- .saturating_add(RocksDbWeight::get().reads(4 as Weight))
- .saturating_add(RocksDbWeight::get().writes(4 as Weight))
- }
- fn redeem_by_claim_unbonding() -> Weight {
- (89_000_000 as Weight)
- .saturating_add(RocksDbWeight::get().reads(7 as Weight))
- .saturating_add(RocksDbWeight::get().writes(5 as Weight))
- }
- fn withdraw_redemption() -> Weight {
- (64_000_000 as Weight)
- .saturating_add(RocksDbWeight::get().reads(5 as Weight))
- .saturating_add(RocksDbWeight::get().writes(3 as Weight))
- }
-}
diff --git a/modules/relaychain/src/lib.rs b/modules/relaychain/src/lib.rs
index d8fecc4c2a..ce4ec27a82 100644
--- a/modules/relaychain/src/lib.rs
+++ b/modules/relaychain/src/lib.rs
@@ -39,7 +39,10 @@ use frame_system::Config;
#[derive(Encode, Decode, RuntimeDebug)]
pub enum BalancesCall {
#[codec(index = 3)]
- TransferKeepAlive(::Source, #[codec(compact)] Balance),
+ TransferKeepAlive(::Source, #[codec(compact)] Balance), /* TODO: because param type
+ * in relaychain is u64,
+ * need to confirm
+ * Balance(u128) is work. */
}
#[derive(Encode, Decode, RuntimeDebug)]
@@ -52,6 +55,12 @@ pub enum UtilityCall {
#[derive(Encode, Decode, RuntimeDebug)]
pub enum StakingCall {
+ #[codec(index = 1)]
+ BondExtra(#[codec(compact)] Balance), /* TODO: because param type in relaychain is u64, need to confirm
+ * Balance(u128) is work. */
+ #[codec(index = 2)]
+ Unbond(#[codec(compact)] Balance), /* TODO: because param type in relaychain is u64, need to confirm
+ * Balance(u128) is work. */
#[codec(index = 3)]
WithdrawUnbonded(u32),
}
@@ -115,6 +124,14 @@ where
RelayChainCall::Utility(Box::new(UtilityCall::AsDerivative(index, call)))
}
+ fn staking_bond_extra(amount: Self::Balance) -> Self::RelayChainCall {
+ RelayChainCall::Staking(StakingCall::BondExtra(amount))
+ }
+
+ fn staking_unbond(amount: Self::Balance) -> Self::RelayChainCall {
+ RelayChainCall::Staking(StakingCall::Unbond(amount))
+ }
+
fn staking_withdraw_unbonded(num_slashing_spans: u32) -> Self::RelayChainCall {
RelayChainCall::Staking(StakingCall::WithdrawUnbonded(num_slashing_spans))
}
diff --git a/modules/support/src/lib.rs b/modules/support/src/lib.rs
index c1de5b1b6d..806caa4bf7 100644
--- a/modules/support/src/lib.rs
+++ b/modules/support/src/lib.rs
@@ -597,6 +597,16 @@ pub trait CallBuilder {
/// - index: The index of sub-account to be used as the new origin.
fn utility_as_derivative_call(call: Self::RelayChainCall, index: u16) -> Self::RelayChainCall;
+ /// Bond extra on relay-chain.
+ /// params:
+ /// - amount: The amount of staking currency to bond.
+ fn staking_bond_extra(amount: Self::Balance) -> Self::RelayChainCall;
+
+ /// Unbond on relay-chain.
+ /// params:
+ /// - amount: The amount of staking currency to unbond.
+ fn staking_unbond(amount: Self::Balance) -> Self::RelayChainCall;
+
/// Withdraw unbonded staking on the relay-chain.
/// params:
/// - num_slashing_spans: The number of slashing spans to withdraw from.
diff --git a/node/service/src/chain_spec/mandala.rs b/node/service/src/chain_spec/mandala.rs
index e9b348edda..269ed77738 100644
--- a/node/service/src/chain_spec/mandala.rs
+++ b/node/service/src/chain_spec/mandala.rs
@@ -240,7 +240,7 @@ fn testnet_genesis(
DexConfig, EVMConfig, EnabledTradingPairs, FinancialCouncilMembershipConfig, GeneralCouncilMembershipConfig,
HomaCouncilMembershipConfig, IndicesConfig, NativeTokenExistentialDeposit, OperatorMembershipAcalaConfig,
OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, RenVmBridgeConfig, SessionConfig, SessionDuration,
- SessionKeys, SessionManagerConfig, StakingPoolConfig, StarportConfig, SudoConfig, SystemConfig,
+ SessionKeys, SessionManagerConfig, StarportConfig, SudoConfig, SystemConfig,
TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, AUSD, DOT, LDOT, RENBTC,
};
@@ -362,15 +362,6 @@ fn testnet_genesis(
accounts: evm_genesis_accounts,
treasury: root_key,
},
- staking_pool: StakingPoolConfig {
- staking_pool_params: module_staking_pool::Params {
- target_max_free_unbonded_ratio: FixedU128::saturating_from_rational(10, 100),
- target_min_free_unbonded_ratio: FixedU128::saturating_from_rational(5, 100),
- target_unbonding_to_free_ratio: FixedU128::saturating_from_rational(2, 100),
- unbonding_to_free_adjustment: FixedU128::saturating_from_rational(1, 1000),
- base_fee_rate: FixedU128::saturating_from_rational(2, 100),
- },
- },
dex: DexConfig {
initial_listing_trading_pairs: vec![],
initial_enabled_trading_pairs: EnabledTradingPairs::get(),
@@ -441,9 +432,8 @@ fn mandala_genesis(
CollatorSelectionConfig, DexConfig, EVMConfig, EnabledTradingPairs, FinancialCouncilMembershipConfig,
GeneralCouncilMembershipConfig, HomaCouncilMembershipConfig, IndicesConfig, NativeTokenExistentialDeposit,
OperatorMembershipAcalaConfig, OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, RenVmBridgeConfig,
- SessionConfig, SessionDuration, SessionKeys, SessionManagerConfig, StakingPoolConfig, StarportConfig,
- SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, AUSD, DOT,
- LDOT, RENBTC,
+ SessionConfig, SessionDuration, SessionKeys, SessionManagerConfig, StarportConfig, SudoConfig, SystemConfig,
+ TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, AUSD, DOT, LDOT, RENBTC,
};
let existential_deposit = NativeTokenExistentialDeposit::get();
@@ -561,15 +551,6 @@ fn mandala_genesis(
accounts: evm_genesis_accounts,
treasury: root_key,
},
- staking_pool: StakingPoolConfig {
- staking_pool_params: module_staking_pool::Params {
- target_max_free_unbonded_ratio: FixedU128::saturating_from_rational(10, 100),
- target_min_free_unbonded_ratio: FixedU128::saturating_from_rational(5, 100),
- target_unbonding_to_free_ratio: FixedU128::saturating_from_rational(2, 100),
- unbonding_to_free_adjustment: FixedU128::saturating_from_rational(1, 1000),
- base_fee_rate: FixedU128::saturating_from_rational(2, 100),
- },
- },
dex: DexConfig {
initial_listing_trading_pairs: vec![],
initial_enabled_trading_pairs: EnabledTradingPairs::get(),
diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs
index cbd5e73547..36c778f7e4 100644
--- a/runtime/karura/src/lib.rs
+++ b/runtime/karura/src/lib.rs
@@ -1598,10 +1598,10 @@ parameter_types! {
ParachainInfo::get().into_account(),
RelayChainSubAccountId::HomaLite as u16
);
- pub MaxRewardPerEra: Permill = Permill::from_rational(500u32, 1_000_000u32); // 1.2 ^ (1/365) = 1.0004996359
+ pub MaxRewardPerEra: Permill = Permill::from_rational(500u32, 1_000_000u32); // 1.2 ^ (1/365) - 1 ≈ 0.05%
pub MintFee: Balance = 20 * millicent(KSM); // 2x XCM fee on Kusama
pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10);
- pub BaseWithdrawFee: Permill = Permill::from_rational(35u32, 10_000u32); // 20% yield per year, unbonding period = 7 days. 1.2^(7 / 365) = 1.00350
+ pub BaseWithdrawFee: Permill = Permill::from_rational(35u32, 10_000u32); // 20% yield per year, unbonding period = 7 days. 1.2^(7 / 365) - 1 ≈ 0.35%
pub MaximumRedeemRequestMatchesForMint: u32 = 20;
pub RelayChainUnbondingSlashingSpans: u32 = 5;
pub MaxScheduledUnbonds: u32 = 14;
diff --git a/runtime/mandala/Cargo.toml b/runtime/mandala/Cargo.toml
index 063430a6e4..b2ee4e85f5 100644
--- a/runtime/mandala/Cargo.toml
+++ b/runtime/mandala/Cargo.toml
@@ -113,12 +113,8 @@ module-incentives = { path = "../../modules/incentives", default-features = fals
module-support = { path = "../../modules/support", default-features = false }
module-homa = { path = "../../modules/homa", default-features = false }
module-homa-lite = { path = "../../modules/homa-lite", default-features = false }
-module-homa-validator-list = { path = "../../modules/homa-validator-list", default-features = false }
module-nominees-election = { path = "../../modules/nominees-election", default-features = false }
module-session-manager = { path = "../../modules/session-manager", default-features = false }
-module-staking-pool = { path = "../../modules/staking-pool", default-features = false }
-module-staking-pool-rpc-runtime-api = { path = "../../modules/staking-pool/rpc/runtime-api", default-features = false }
-module-polkadot-bridge = { path = "../../modules/polkadot-bridge", default-features = false }
module-relaychain = { path = "../../modules/relaychain", default-features = false, features = ["polkadot"]}
module-idle-scheduler = { path = "../../modules/idle-scheduler", default-features = false }
nutsfinance-stable-asset = { version = "0.1.0", default-features = false, path = "../../ecosystem-modules/stable-asset/lib/stable-asset", package = "nutsfinance-stable-asset" }
@@ -247,13 +243,9 @@ std = [
"module-prices/std",
"module-incentives/std",
"module-support/std",
- "module-homa/std",
"module-homa-lite/std",
"module-nominees-election/std",
"module-session-manager/std",
- "module-staking-pool/std",
- "module-staking-pool-rpc-runtime-api/std",
- "module-polkadot-bridge/std",
"module-relaychain/std",
"module-idle-scheduler/std",
"primitives/std",
@@ -349,12 +341,9 @@ try-runtime = [
"module-nft/try-runtime",
"module-prices/try-runtime",
"module-incentives/try-runtime",
- "module-homa/try-runtime",
"module-homa-lite/try-runtime",
"module-nominees-election/try-runtime",
"module-session-manager/try-runtime",
- "module-staking-pool/try-runtime",
- "module-polkadot-bridge/try-runtime",
"ecosystem-renvm-bridge/try-runtime",
"ecosystem-starport/try-runtime",
diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs
index 2b2538c2d8..4ab72fc2dc 100644
--- a/runtime/mandala/src/lib.rs
+++ b/runtime/mandala/src/lib.rs
@@ -837,20 +837,13 @@ impl module_prices::Config for Runtime {
type GetStakingCurrencyId = GetStakingCurrencyId;
type GetLiquidCurrencyId = GetLiquidCurrencyId;
type LockOrigin = EnsureRootOrTwoThirdsGeneralCouncil;
- type LiquidStakingExchangeRateProvider = LiquidStakingExchangeRateProvider;
+ type LiquidStakingExchangeRateProvider = HomaLite;
type DEX = Dex;
type Currency = Currencies;
type Erc20InfoMapping = EvmErc20InfoMapping;
type WeightInfo = weights::module_prices::WeightInfo;
}
-pub struct LiquidStakingExchangeRateProvider;
-impl module_support::ExchangeRateProvider for LiquidStakingExchangeRateProvider {
- fn get_exchange_rate() -> ExchangeRate {
- StakingPool::liquid_exchange_rate()
- }
-}
-
parameter_types! {
pub const GetNativeCurrencyId: CurrencyId = ACA;
pub const GetStableCurrencyId: CurrencyId = AUSD;
@@ -1198,45 +1191,10 @@ impl module_incentives::Config for Runtime {
type WeightInfo = weights::module_incentives::WeightInfo;
}
-parameter_types! {
- pub const PolkadotBondingDuration: EraIndex = 7;
- pub const EraLength: BlockNumber = DAYS;
- pub const MaxUnbonding: u32 = 1000;
-}
-
-impl module_polkadot_bridge::Config for Runtime {
- type DOTCurrency = Currency;
- type OnNewEra = (NomineesElection, StakingPool);
- type BondingDuration = PolkadotBondingDuration;
- type EraLength = EraLength;
- type PolkadotAccountId = AccountId;
- type MaxUnbonding = MaxUnbonding;
-}
-
parameter_types! {
pub const GetLiquidCurrencyId: CurrencyId = LDOT;
pub const GetStakingCurrencyId: CurrencyId = DOT;
pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(10, 100); // 1 : 10
- pub PoolAccountIndexes: Vec = vec![1, 2, 3, 4];
-}
-
-impl module_staking_pool::Config for Runtime {
- type Event = Event;
- type StakingCurrencyId = GetStakingCurrencyId;
- type LiquidCurrencyId = GetLiquidCurrencyId;
- type DefaultExchangeRate = DefaultExchangeRate;
- type PalletId = StakingPoolPalletId;
- type PoolAccountIndexes = PoolAccountIndexes;
- type UpdateOrigin = EnsureRootOrHalfHomaCouncil;
- type FeeModel = CurveFeeModel;
- type Nominees = NomineesElection;
- type Bridge = PolkadotBridge;
- type Currency = Currencies;
-}
-
-impl module_homa::Config for Runtime {
- type Homa = StakingPool;
- type WeightInfo = weights::module_homa::WeightInfo;
}
pub fn create_x2_parachain_multilocation(index: u16) -> MultiLocation {
@@ -1315,28 +1273,6 @@ impl module_nominees_election::Config for Runtime {
type WeightInfo = weights::module_nominees_election::WeightInfo;
}
-parameter_types! {
- pub MinGuaranteeAmount: Balance = dollar(LDOT);
- pub const ValidatorInsuranceThreshold: Balance = 0;
-}
-
-impl module_homa_validator_list::Config for Runtime {
- type Event = Event;
- type RelaychainAccountId = AccountId;
- type LiquidTokenCurrency = Currency;
- type MinBondAmount = MinGuaranteeAmount;
- type BondingDuration = PolkadotBondingDuration;
- type ValidatorInsuranceThreshold = ValidatorInsuranceThreshold;
- type FreezeOrigin = EnsureRootOrHalfHomaCouncil;
- type SlashOrigin = EnsureRootOrHalfHomaCouncil;
- type OnSlash = module_staking_pool::OnSlash;
- type LiquidStakingExchangeRateProvider = LiquidStakingExchangeRateProvider;
- type WeightInfo = ();
- type OnIncreaseGuarantee = ();
- type OnDecreaseGuarantee = ();
- type BlockNumberProvider = RelayChainBlockNumberProvider;
-}
-
parameter_types! {
pub CreateClassDeposit: Balance = 20 * dollar(ACA);
pub CreateTokenDeposit: Balance = 2 * dollar(ACA);
@@ -2099,11 +2035,7 @@ construct_runtime! {
EmergencyShutdown: module_emergency_shutdown::{Pallet, Storage, Call, Event} = 125,
// Homa
- Homa: module_homa::{Pallet, Call} = 130,
NomineesElection: module_nominees_election::{Pallet, Call, Storage, Event} = 131,
- StakingPool: module_staking_pool::{Pallet, Call, Storage, Event, Config} = 132,
- PolkadotBridge: module_polkadot_bridge::{Pallet, Call, Storage} = 133,
- HomaValidatorListModule: module_homa_validator_list::{Pallet, Call, Storage, Event} = 134,
HomaLite: module_homa_lite::{Pallet, Call, Storage, Event} = 135,
// Acala Other
@@ -2270,22 +2202,6 @@ impl_runtime_apis! {
}
}
- impl module_staking_pool_rpc_runtime_api::StakingPoolApi<
- Block,
- AccountId,
- Balance,
- > for Runtime {
- fn get_available_unbonded(account: AccountId) -> module_staking_pool_rpc_runtime_api::BalanceInfo {
- module_staking_pool_rpc_runtime_api::BalanceInfo {
- amount: StakingPool::get_available_unbonded(&account)
- }
- }
-
- fn get_liquid_staking_exchange_rate() -> ExchangeRate {
- StakingPool::liquid_exchange_rate()
- }
- }
-
impl module_evm_rpc_runtime_api::EVMRuntimeRPCApi for Runtime {
fn call(
from: H160,
diff --git a/runtime/mandala/src/weights/mod.rs b/runtime/mandala/src/weights/mod.rs
index 51e957343e..b825f4fdcc 100644
--- a/runtime/mandala/src/weights/mod.rs
+++ b/runtime/mandala/src/weights/mod.rs
@@ -29,7 +29,6 @@ pub mod module_dex;
pub mod module_emergency_shutdown;
pub mod module_evm;
pub mod module_evm_accounts;
-pub mod module_homa;
pub mod module_homa_lite;
pub mod module_honzon;
pub mod module_incentives;
diff --git a/runtime/mandala/src/weights/module_homa.rs b/runtime/mandala/src/weights/module_homa.rs
deleted file mode 100644
index 325ab33eee..0000000000
--- a/runtime/mandala/src/weights/module_homa.rs
+++ /dev/null
@@ -1,75 +0,0 @@
-// This file is part of Acala.
-
-// Copyright (C) 2020-2021 Acala Foundation.
-// 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_homa
-//!
-//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0
-//! DATE: 2021-07-19, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
-//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
-
-// Executed Command:
-// target/release/acala
-// benchmark
-// --chain=dev
-// --steps=50
-// --repeat=20
-// --pallet=*
-// --extrinsic=*
-// --execution=wasm
-// --wasm-execution=compiled
-// --heap-pages=4096
-// --template=./templates/runtime-weight-template.hbs
-// --output=./runtime/mandala/src/weights/
-
-
-#![cfg_attr(rustfmt, rustfmt_skip)]
-#![allow(unused_parens)]
-#![allow(unused_imports)]
-
-use frame_support::{traits::Get, weights::Weight};
-use sp_std::marker::PhantomData;
-
-/// Weight functions for module_homa.
-pub struct WeightInfo(PhantomData);
-impl module_homa::WeightInfo for WeightInfo {
- fn mint() -> Weight {
- (195_776_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(9 as Weight))
- .saturating_add(T::DbWeight::get().writes(7 as Weight))
- }
- fn redeem_immediately() -> Weight {
- (197_625_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(8 as Weight))
- .saturating_add(T::DbWeight::get().writes(5 as Weight))
- }
- fn redeem_wait_for_unbonding() -> Weight {
- (89_362_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(5 as Weight))
- .saturating_add(T::DbWeight::get().writes(4 as Weight))
- }
- fn redeem_by_claim_unbonding() -> Weight {
- (146_082_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(8 as Weight))
- .saturating_add(T::DbWeight::get().writes(5 as Weight))
- }
- fn withdraw_redemption() -> Weight {
- (111_068_000 as Weight)
- .saturating_add(T::DbWeight::get().reads(6 as Weight))
- .saturating_add(T::DbWeight::get().writes(3 as Weight))
- }
-}
From d44505e5e28ca3f712d8218176c97e634d96234d Mon Sep 17 00:00:00 2001
From: wangjj9219 <183318287@qq.com>
Date: Mon, 29 Nov 2021 19:57:07 +0800
Subject: [PATCH 02/27] update
---
modules/homa/src/lib.rs | 44 ++---
modules/homa/src/mock.rs | 380 ++++++++++++++++++++++++++++++++++++++
modules/homa/src/tests.rs | 19 ++
3 files changed, 422 insertions(+), 21 deletions(-)
create mode 100644 modules/homa/src/mock.rs
create mode 100644 modules/homa/src/tests.rs
diff --git a/modules/homa/src/lib.rs b/modules/homa/src/lib.rs
index d406ce5ba1..fa6a8ee7ac 100644
--- a/modules/homa/src/lib.rs
+++ b/modules/homa/src/lib.rs
@@ -16,15 +16,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+//! Homa module.
+
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::unused_unit)]
use frame_support::{log, pallet_prelude::*, transactional, weights::Weight, BoundedVec, PalletId};
use frame_system::{ensure_signed, pallet_prelude::*};
use module_support::{CallBuilder, ExchangeRate, ExchangeRateProvider, Rate};
-use orml_traits::{
- arithmetic::Signed, BalanceStatus, MultiCurrency, MultiCurrencyExtended, MultiReservableCurrency, XcmTransfer,
-};
+use orml_traits::{arithmetic::Signed, BalanceStatus, MultiCurrency, MultiCurrencyExtended, XcmTransfer};
use pallet_staking::EraIndex;
use primitives::{Balance, CurrencyId};
use scale_info::TypeInfo;
@@ -44,6 +44,9 @@ use xcm::latest::prelude::*;
pub use module::*;
+mod mock;
+mod tests;
+
#[frame_support::pallet]
pub mod module {
use super::*;
@@ -98,16 +101,13 @@ pub mod module {
}
pub type SubAccountIndex = u16;
- pub(crate) type AmountOf =
- <::Currency as MultiCurrencyExtended<::AccountId>>::Amount;
#[pallet::config]
pub trait Config: frame_system::Config + pallet_xcm::Config {
type Event: From> + IsType<::Event>;
/// Multi-currency support for asset management
- type Currency: MultiReservableCurrency
- + MultiCurrencyExtended;
+ type Currency: MultiCurrencyExtended;
/// Origin represented Governance
type GovernanceOrigin: EnsureOrigin<::Origin>;
@@ -237,7 +237,7 @@ pub mod module {
#[pallet::getter(fn relay_chain_current_era)]
pub type RelayChainCurrentEra = StorageValue<_, EraIndex, ValueQuery>;
- // /// The latest processed era on by, it should be always <= RelayChainCurrentEra
+ // /// The latest processed era of Homa, it should be always <= RelayChainCurrentEra
// ///
// /// ProcessedEra : EraIndex
// #[pallet::storage]
@@ -261,7 +261,8 @@ pub mod module {
/// The total amount of void liquid currency. It's will not be issued,
/// used to avoid newly issued LDOT to obtain the incoming staking income from relaychain.
/// And it is guaranteed that the current exchange rate between liquid currency and staking
- /// currency will not change. It will be reset to 0 at the end of the rebalance when new era.
+ /// currency will not change. It will be reset to 0 at the beginning of the rebalance when new
+ /// era.
///
/// TotalVoidLiquid value: LiquidCurrencyAmount
#[pallet::storage]
@@ -289,8 +290,9 @@ pub mod module {
pub type Unbondings =
StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, EraIndex, Balance, ValueQuery>;
- /// The weight for excution XCM msg on relaychain. Must be higher than
- /// T::BaseXcmWeight + T::Weigher::weight(xcm_message_sended_by_homa)` on relaychain.
+ /// The weight limit for excution XCM msg on relaychain. Must be greater than the weight of
+ /// the XCM msg that sended by Homa, otherwise the execution of XCM msg will fail.
+ /// Consider all possible xcm msgs sended by Homa, and use the maximum as the limit.
///
/// xcm_dest_weight: value: Weight
#[pallet::storage]
@@ -304,6 +306,7 @@ pub mod module {
impl Hooks for Pallet {
fn integrity_test() {
assert!(!T::DefaultExchangeRate::get().is_zero());
+ assert!(T::MintThreshold::get() >= T::XcmTransferFee::get());
}
}
@@ -321,14 +324,13 @@ pub mod module {
// Ensure the amount is above the mint threshold.
ensure!(amount > T::MintThreshold::get(), Error::::BelowMintThreshold);
- // TODO: is that neccesary?
+ // Ensure the total staking currency will not exceed soft cap.
ensure!(
Self::get_total_staking_currency().saturating_add(amount) <= Self::get_staking_currency_soft_cap(),
Error::::ExceededStakingCurrencySoftCap
);
T::Currency::transfer(T::StakingCurrencyId::get(), &minter, &Self::account_id(), amount)?;
- ToBondPool::::mutate(|pool| *pool = pool.saturating_add(amount));
// calculate the liquid amount by the current exchange rate.
let liquid_amount = Self::convert_staking_to_liquid(amount)?;
@@ -340,6 +342,7 @@ pub mod module {
let liquid_add_to_void = liquid_amount.saturating_sub(liquid_issue_to_minter);
T::Currency::deposit(T::LiquidCurrencyId::get(), &minter, liquid_issue_to_minter)?;
+ ToBondPool::::mutate(|pool| *pool = pool.saturating_add(amount));
TotalVoidLiquid::::mutate(|total| *total = total.saturating_add(liquid_add_to_void));
Self::deposit_event(Event::::Minted(minter, amount, liquid_issue_to_minter));
@@ -484,10 +487,10 @@ pub mod module {
ensure!(new_era > *current_era, Error::::InvalidEraIndex);
*current_era = new_era;
- // reset void liquid to zero
+ // reset void liquid to zero firstly, to guarantee
TotalVoidLiquid::::put(0);
- // TODO: consider execute rebalance on on_idle, tut before the processing is completed,
+ // TODO: consider execute rebalance on on_idle, before the processing is completed,
// the mint and request_redeem functions should be unavailable.
// Rebalance:
Self::process_scheduled_unbond(new_era)?;
@@ -590,11 +593,9 @@ pub mod module {
/// Calculate the total amount of staking currency belong to Homa.
pub fn get_total_staking_currency() -> Balance {
- let total_bonded: Balance = StakingLedgers::::iter().fold(Zero::zero(), |total_bonded, (_, ledger)| {
+ StakingLedgers::::iter().fold(Self::to_bond_pool(), |total_bonded, (_, ledger)| {
total_bonded.saturating_add(ledger.bonded)
- });
- let to_bond_pool = Self::to_bond_pool();
- total_bonded.saturating_add(to_bond_pool)
+ })
}
/// Calculate the current exchange rate between the staking currency and liquid currency.
@@ -851,8 +852,8 @@ pub mod module {
Ok(())
}
- /// Cross-chain transfer staking currency to subaccount and bond it on relaychain by XCM
- /// message.
+ /// Cross-chain transfer staking currency to subaccount and send XCM message to the
+ /// relaychain to bond it.
pub fn transfer_and_bond_to_relaychain(sub_account_index: SubAccountIndex, amount: Balance) -> DispatchResult {
T::XcmTransfer::transfer(
Self::account_id(),
@@ -862,6 +863,7 @@ pub mod module {
Self::xcm_dest_weight(),
)?;
+ // subaccount will pay the XcmTransferFee, so the actual staking amount received should deduct it.
let bond_amount = amount.saturating_sub(T::XcmTransferFee::get());
let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message(
T::RelayChainCallBuilder::utility_as_derivative_call(
diff --git a/modules/homa/src/mock.rs b/modules/homa/src/mock.rs
new file mode 100644
index 0000000000..59e7a262dc
--- /dev/null
+++ b/modules/homa/src/mock.rs
@@ -0,0 +1,380 @@
+// This file is part of Acala.
+
+// Copyright (C) 2020-2021 Acala Foundation.
+// 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 Homa module.
+
+#![cfg(test)]
+
+use super::*;
+use cumulus_primitives_core::ParaId;
+use frame_support::{
+ ord_parameter_types, parameter_types,
+ traits::{Everything, Nothing},
+};
+use frame_system::{EnsureRoot, EnsureSignedBy, RawOrigin};
+use module_relaychain::RelayChainCallBuilder;
+use module_support::mocks::MockAddressMapping;
+use orml_traits::{parameter_type_with_key, XcmTransfer};
+use primitives::{Amount, TokenSymbol};
+use sp_core::H256;
+use sp_io::hashing::blake2_256;
+use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32};
+use xcm::latest::prelude::*;
+use xcm_executor::traits::{InvertLocation, WeightBounds};
+
+pub type AccountId = AccountId32;
+pub type BlockNumber = u64;
+
+mod homa {
+ pub use super::super::*;
+}
+
+pub const ALICE: AccountId = AccountId32::new([1u8; 32]);
+pub const BOB: AccountId = AccountId32::new([2u8; 32]);
+pub const CHARLIE: AccountId = AccountId32::new([3u8; 32]);
+pub const DAVE: AccountId = AccountId32::new([255u8; 32]);
+pub const INVALID_CALLER: AccountId = AccountId32::new([254u8; 32]);
+pub const ACA: CurrencyId = CurrencyId::Token(TokenSymbol::ACA);
+pub const DOT: CurrencyId = CurrencyId::Token(TokenSymbol::DOT);
+pub const LDOT: CurrencyId = CurrencyId::Token(TokenSymbol::LDOT);
+pub const MOCK_XCM_ACCOUNTID: AccountId = AccountId32::new([255u8; 32]);
+pub const PARACHAIN_ID: u32 = 2000;
+
+/// For testing only. Does not check for overflow.
+pub fn dollar(b: Balance) -> Balance {
+ b * 1_000_000_000_000
+}
+
+/// For testing only. Does not check for overflow.
+pub fn cent(b: Balance) -> Balance {
+ b * 10_000_000_000
+}
+
+/// mock XCM transfer.
+pub struct MockXcmTransfer;
+impl XcmTransfer for MockXcmTransfer {
+ fn transfer(
+ who: AccountId,
+ currency_id: CurrencyId,
+ amount: Balance,
+ dest: MultiLocation,
+ _dest_weight: Weight,
+ ) -> DispatchResult {
+ match who {
+ INVALID_CALLER => Err(DispatchError::Other("invalid caller")),
+ _ => Ok(()),
+ }?;
+ match currency_id {
+ ACA => Err(DispatchError::Other("unacceptable currency id")),
+ _ => Ok(()),
+ }?;
+
+ Currencies::withdraw(ACA, &who, amount)
+ }
+
+ fn transfer_multi_asset(
+ _who: AccountId,
+ _asset: MultiAsset,
+ _dest: MultiLocation,
+ _dest_weight: Weight,
+ ) -> DispatchResult {
+ unimplemented!()
+ }
+}
+
+/// mock XCM.
+pub struct MockXcm;
+impl InvertLocation for MockXcm {
+ fn invert_location(l: &MultiLocation) -> Result {
+ Ok(l.clone())
+ }
+}
+
+impl SendXcm for MockXcm {
+ fn send_xcm(dest: impl Into, msg: Xcm<()>) -> SendResult {
+ let dest = dest.into();
+ match dest {
+ MultiLocation {
+ parents: 1,
+ interior: Junctions::Here,
+ } => Ok(()),
+ _ => Err(SendError::CannotReachDestination(dest, msg)),
+ }
+ }
+}
+
+impl ExecuteXcm for MockXcm {
+ fn execute_xcm_in_credit(
+ _origin: impl Into,
+ mut _message: Xcm,
+ _weight_limit: Weight,
+ _weight_credit: Weight,
+ ) -> Outcome {
+ Outcome::Complete(0)
+ }
+}
+
+pub struct MockEnsureXcmOrigin;
+impl EnsureOrigin for MockEnsureXcmOrigin {
+ type Success = MultiLocation;
+ fn try_origin(_o: Origin) -> Result {
+ Ok(MultiLocation::here())
+ }
+
+ #[cfg(feature = "runtime-benchmarks")]
+ fn successful_origin() -> Origin {
+ Origin::from(RawOrigin::Signed(Default::default()))
+ }
+}
+pub struct MockWeigher;
+impl WeightBounds for MockWeigher {
+ fn weight(_message: &mut Xcm) -> Result {
+ Ok(0)
+ }
+
+ fn instr_weight(_message: &Instruction) -> Result {
+ Ok(0)
+ }
+}
+
+impl pallet_xcm::Config for Runtime {
+ type Event = Event;
+ type SendXcmOrigin = MockEnsureXcmOrigin;
+ type XcmRouter = MockXcm;
+ type ExecuteXcmOrigin = MockEnsureXcmOrigin;
+ type XcmExecuteFilter = Nothing;
+ type XcmExecutor = MockXcm;
+ type XcmTeleportFilter = Everything;
+ type XcmReserveTransferFilter = Everything;
+ type Weigher = MockWeigher;
+ type LocationInverter = MockXcm;
+ type Origin = Origin;
+ type Call = Call;
+ const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
+ type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
+}
+
+parameter_types! {
+ pub const BlockHashCount: u64 = 250;
+}
+
+impl frame_system::Config for Runtime {
+ type BaseCallFilter = Everything;
+ type BlockWeights = ();
+ type BlockLength = ();
+ type Origin = Origin;
+ type Call = Call;
+ type Index = u64;
+ type BlockNumber = BlockNumber;
+ type Hash = H256;
+ type Hashing = ::sp_runtime::traits::BlakeTwo256;
+ type AccountId = AccountId;
+ type Lookup = IdentityLookup;
+ type Header = Header;
+ type Event = Event;
+ type BlockHashCount = BlockHashCount;
+ type DbWeight = ();
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = pallet_balances::AccountData;
+ type OnNewAccount = ();
+ type OnKilledAccount = ();
+ type SystemWeightInfo = ();
+ type SS58Prefix = ();
+ type OnSetCode = ();
+}
+
+parameter_type_with_key! {
+ pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance {
+ Default::default()
+ };
+}
+
+impl orml_tokens::Config for Runtime {
+ type Event = Event;
+ type Balance = Balance;
+ type Amount = Amount;
+ type CurrencyId = CurrencyId;
+ type WeightInfo = ();
+ type ExistentialDeposits = ExistentialDeposits;
+ type OnDust = ();
+ type MaxLocks = ();
+ type DustRemovalWhitelist = Nothing;
+}
+
+parameter_types! {
+ pub const NativeTokenExistentialDeposit: Balance = 0;
+}
+
+impl pallet_balances::Config for Runtime {
+ type Balance = Balance;
+ type DustRemoval = ();
+ type Event = Event;
+ type ExistentialDeposit = NativeTokenExistentialDeposit;
+ type AccountStore = frame_system::Pallet;
+ type MaxLocks = ();
+ type WeightInfo = ();
+ type MaxReserves = ();
+ type ReserveIdentifier = ();
+}
+
+pub type AdaptedBasicCurrency = module_currencies::BasicCurrencyAdapter;
+
+parameter_types! {
+ pub const GetNativeCurrencyId: CurrencyId = ACA;
+}
+
+impl module_currencies::Config for Runtime {
+ type Event = Event;
+ type MultiCurrency = Tokens;
+ type NativeCurrency = AdaptedBasicCurrency;
+ type GetNativeCurrencyId = GetNativeCurrencyId;
+ type WeightInfo = ();
+ type AddressMapping = MockAddressMapping;
+ type EVMBridge = ();
+ type SweepOrigin = EnsureRoot;
+ type OnDust = ();
+}
+
+pub struct MockConvertor;
+impl Convert for MockConvertor {
+ fn convert(index: SubAccountIndex) -> MultiLocation {
+ let entropy = (b"modlpy/utilisuba", ParachainAccount::get(), index).using_encoded(blake2_256);
+ let subaccount = AccountId32::decode(&mut &entropy[..]).unwrap_or_default();
+ MultiLocation::new(
+ 1,
+ X1(Junction::AccountId32 {
+ network: NetworkId::Any,
+ id: subaccount.into(),
+ }),
+ )
+ }
+}
+
+ord_parameter_types! {
+ pub const HomaAdmin: AccountId = DAVE;
+}
+
+parameter_types! {
+ pub const LiquidCurrencyId: CurrencyId = LDOT;
+ pub const HomaPalletId: PalletId = PalletId(*b"aca/homa");
+ pub MintThreshold: Balance = dollar(1);
+ pub RedeemThreshold: Balance = dollar(10);
+ pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10);
+ pub ParachainAccount: AccountId = AccountId32::new([250u8; 32]);
+ pub ActiveSubAccountsIndexList: Vec = vec![0, 1, 2];
+ pub SoftBondedCapPerSubAccount: Balance = dollar(10);
+ pub FastMatchKeepers: Vec = vec![CHARLIE, DAVE];
+ pub const BondingDuration: EraIndex = 28;
+ pub const RelayChainUnbondingSlashingSpans: EraIndex = 7;
+ pub EstimatedRewardRatePerEra: Rate = Rate::saturating_from_rational(1, 100);
+ pub XcmTransferFee: Balance = cent(50);
+ pub XcmMessageFee: Balance = cent(50);
+ pub ParachainId: ParaId = ParaId::from(PARACHAIN_ID);
+}
+
+impl Config for Runtime {
+ type Event = Event;
+ type Currency = Currencies;
+ type GovernanceOrigin = EnsureSignedBy;
+ type StakingCurrencyId = GetNativeCurrencyId;
+ type LiquidCurrencyId = LiquidCurrencyId;
+ type PalletId = HomaPalletId;
+ type DefaultExchangeRate = DefaultExchangeRate;
+ type MintThreshold = MintThreshold;
+ type RedeemThreshold = RedeemThreshold;
+ type ParachainAccount = ParachainAccount;
+ type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList;
+ type SoftBondedCapPerSubAccount = SoftBondedCapPerSubAccount;
+ type FastMatchKeepers = FastMatchKeepers;
+ type BondingDuration = BondingDuration;
+ type RelayChainUnbondingSlashingSpans = RelayChainUnbondingSlashingSpans;
+ type EstimatedRewardRatePerEra = EstimatedRewardRatePerEra;
+ type XcmTransferFee = XcmTransferFee;
+ type XcmMessageFee = XcmMessageFee;
+ type RelayChainCallBuilder = RelayChainCallBuilder;
+ type XcmTransfer = MockXcmTransfer;
+ type SovereignSubAccountLocationConvert = MockConvertor;
+}
+
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic;
+type Block = frame_system::mocking::MockBlock;
+
+frame_support::construct_runtime!(
+ pub enum Runtime where
+ Block = Block,
+ NodeBlock = Block,
+ UncheckedExtrinsic = UncheckedExtrinsic
+ {
+ System: frame_system::{Pallet, Call, Config, Storage, Event},
+ Homa: homa::{Pallet, Call, Storage, Event},
+ Balances: pallet_balances::{Pallet, Call, Storage, Config, Event},
+ Tokens: orml_tokens::{Pallet, Storage, Event, Config},
+ Currencies: module_currencies::{Pallet, Call, Event},
+ PalletXcm: pallet_xcm::{Pallet, Call, Event, Origin},
+ }
+);
+
+pub struct ExtBuilder {
+ balances: Vec<(AccountId, CurrencyId, Balance)>,
+}
+
+impl Default for ExtBuilder {
+ fn default() -> Self {
+ Self { balances: vec![] }
+ }
+}
+
+impl ExtBuilder {
+ pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self {
+ self.balances = balances;
+ self
+ }
+
+ pub fn build(self) -> sp_io::TestExternalities {
+ let mut t = frame_system::GenesisConfig::default()
+ .build_storage::()
+ .unwrap();
+
+ pallet_balances::GenesisConfig:: {
+ balances: self
+ .balances
+ .clone()
+ .into_iter()
+ .filter(|(_, currency_id, _)| *currency_id == DOT)
+ .map(|(account_id, _, initial_balance)| (account_id, initial_balance))
+ .collect::>(),
+ }
+ .assimilate_storage(&mut t)
+ .unwrap();
+
+ orml_tokens::GenesisConfig:: {
+ balances: self
+ .balances
+ .into_iter()
+ .filter(|(_, currency_id, _)| *currency_id != DOT)
+ .collect::>(),
+ }
+ .assimilate_storage(&mut t)
+ .unwrap();
+
+ let mut ext = sp_io::TestExternalities::new(t);
+ ext.execute_with(|| System::set_block_number(1));
+ ext
+ }
+}
diff --git a/modules/homa/src/tests.rs b/modules/homa/src/tests.rs
new file mode 100644
index 0000000000..8447de0b69
--- /dev/null
+++ b/modules/homa/src/tests.rs
@@ -0,0 +1,19 @@
+// This file is part of Acala.
+
+// Copyright (C) 2020-2021 Acala Foundation.
+// 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 Homa Module
From cd2fe3cbdcb2d181e79a9939dc579741aa727fb6 Mon Sep 17 00:00:00 2001
From: wangjj9219 <183318287@qq.com>
Date: Tue, 30 Nov 2021 19:05:59 +0800
Subject: [PATCH 03/27] update
---
Cargo.lock | 33 +++-
modules/homa-xcm/Cargo.toml | 56 +++++++
modules/homa-xcm/src/lib.rs | 233 +++++++++++++++++++++++++++
modules/homa/Cargo.toml | 10 --
modules/homa/src/lib.rs | 303 +++++++++++++-----------------------
modules/homa/src/mock.rs | 179 ++++-----------------
modules/support/src/lib.rs | 14 ++
7 files changed, 465 insertions(+), 363 deletions(-)
create mode 100644 modules/homa-xcm/Cargo.toml
create mode 100644 modules/homa-xcm/src/lib.rs
diff --git a/Cargo.lock b/Cargo.lock
index edbb6629be..1501c370f8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5505,27 +5505,21 @@ name = "module-homa"
version = "2.0.3"
dependencies = [
"acala-primitives",
- "cumulus-primitives-core",
"frame-benchmarking",
"frame-support",
"frame-system",
"module-currencies",
- "module-relaychain",
"module-support",
"orml-tokens",
"orml-traits",
"pallet-balances",
"pallet-staking",
- "pallet-xcm",
"parity-scale-codec",
"scale-info",
- "sp-arithmetic",
"sp-core",
"sp-io",
"sp-runtime",
"sp-std",
- "xcm",
- "xcm-executor",
]
[[package]]
@@ -5577,6 +5571,33 @@ dependencies = [
"sp-std",
]
+[[package]]
+name = "module-homa-xcm"
+version = "2.0.3"
+dependencies = [
+ "acala-primitives",
+ "cumulus-primitives-core",
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "module-currencies",
+ "module-relaychain",
+ "module-support",
+ "orml-tokens",
+ "orml-traits",
+ "pallet-balances",
+ "pallet-staking",
+ "pallet-xcm",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+ "xcm",
+ "xcm-executor",
+]
+
[[package]]
name = "module-honzon"
version = "2.0.3"
diff --git a/modules/homa-xcm/Cargo.toml b/modules/homa-xcm/Cargo.toml
new file mode 100644
index 0000000000..dc19e0f4d4
--- /dev/null
+++ b/modules/homa-xcm/Cargo.toml
@@ -0,0 +1,56 @@
+[package]
+name = "module-homa-xcm"
+version = "2.0.3"
+authors = ["Acala Developers"]
+edition = "2018"
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["derive"] }
+scale-info = { version = "1.0", default-features = false, features = ["derive"] }
+frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false, optional = true}
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+pallet-staking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12", default-features = false }
+xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12", default-features = false }
+primitives = { package = "acala-primitives", path = "../../primitives", default-features = false }
+orml-traits = { path = "../../orml/traits", default-features = false }
+module-support = { path = "../../modules/support", default-features = false }
+
+[dev-dependencies]
+sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" }
+pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" }
+module-currencies = { path = "../../modules/currencies" }
+orml-tokens = { path = "../../orml/tokens" }
+xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" }
+cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" }
+module-relaychain = { path = "../relaychain", features = ["kusama"] }
+
+[features]
+default = ["std"]
+std = [
+ "codec/std",
+ "frame-benchmarking/std",
+ "frame-support/std",
+ "frame-system/std",
+ "scale-info/std",
+ "sp-runtime/std",
+ "sp-core/std",
+ "sp-std/std",
+ "pallet-staking/std",
+ "pallet-xcm/std",
+ "xcm/std",
+ "primitives/std",
+ "orml-traits/std",
+ "module-support/std",
+]
+runtime-benchmarks = [
+ "frame-benchmarking",
+ "frame-support/runtime-benchmarks",
+ "frame-system/runtime-benchmarks",
+ "pallet-xcm/runtime-benchmarks",
+]
+try-runtime = ["frame-support/try-runtime"]
diff --git a/modules/homa-xcm/src/lib.rs b/modules/homa-xcm/src/lib.rs
new file mode 100644
index 0000000000..b301351797
--- /dev/null
+++ b/modules/homa-xcm/src/lib.rs
@@ -0,0 +1,233 @@
+// This file is part of Acala.
+
+// Copyright (C) 2020-2021 Acala Foundation.
+// 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 .
+
+//! Homa xcm module.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![allow(clippy::unused_unit)]
+
+use frame_support::{log, pallet_prelude::*, transactional, weights::Weight};
+use frame_system::pallet_prelude::*;
+use module_support::{CallBuilder, HomaSubAccountXcm};
+use orml_traits::XcmTransfer;
+use pallet_staking::EraIndex;
+use primitives::{Balance, CurrencyId};
+use scale_info::TypeInfo;
+use sp_runtime::traits::Convert;
+use sp_std::{convert::From, prelude::*, vec, vec::Vec};
+use xcm::latest::prelude::*;
+
+pub use module::*;
+
+#[frame_support::pallet]
+pub mod module {
+ use super::*;
+
+ #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, TypeInfo)]
+ pub enum HomaXcmOperation {
+ XtokensTransfer,
+ XcmWithdrawUnbonded,
+ XcmBondExtra,
+ XcmUnbond,
+ }
+
+ #[pallet::config]
+ pub trait Config: frame_system::Config + pallet_xcm::Config {
+ type Event: From> + IsType<::Event>;
+
+ /// Origin represented Governance
+ type UpdateOrigin: EnsureOrigin<::Origin>;
+
+ /// The currency id of the Staking asset
+ #[pallet::constant]
+ type StakingCurrencyId: Get;
+
+ /// The account of parachain on the relaychain.
+ #[pallet::constant]
+ type ParachainAccount: Get;
+
+ /// Unbonding slashing spans for unbonding on the relaychain.
+ #[pallet::constant]
+ type RelayChainUnbondingSlashingSpans: Get;
+
+ /// The convert for convert sovereign subacocunt index to the MultiLocation where the
+ /// staking currencies are sent to.
+ type SovereignSubAccountLocationConvert: Convert;
+
+ /// The Call builder for communicating with RelayChain via XCM messaging.
+ type RelayChainCallBuilder: CallBuilder;
+
+ /// The interface to Cross-chain transfer.
+ type XcmTransfer: XcmTransfer;
+ }
+
+ #[pallet::error]
+ pub enum Error {
+ /// The xcm operation have failed
+ XcmFailed,
+ }
+
+ #[pallet::event]
+ #[pallet::generate_deposit(pub(crate) fn deposit_event)]
+ pub enum Event {
+ /// Xcm dest weight has been updated. \[xcm_operation, new_xcm_dest_weight\]
+ XcmDestWeightUpdated(HomaXcmOperation, Weight),
+ /// Xcm dest weight has been updated. \[xcm_operation, new_xcm_dest_weight\]
+ XcmFeeUpdated(HomaXcmOperation, Balance),
+ }
+
+ /// The dest weight limit and fee for execution XCM msg sended by HomaXcm. Must be sufficient,
+ /// otherwise the execution of XCM msg on relaychain will fail.
+ ///
+ /// XcmDestWeightAndFee: map: HomaXcmOperation => (Weight, Balance)
+ #[pallet::storage]
+ #[pallet::getter(fn xcm_dest_weight_and_fee)]
+ pub type XcmDestWeightAndFee =
+ StorageMap<_, Twox64Concat, HomaXcmOperation, (Weight, Balance), ValueQuery>;
+
+ #[pallet::pallet]
+ pub struct Pallet(_);
+
+ #[pallet::hooks]
+ impl Hooks for Pallet {}
+
+ #[pallet::call]
+ impl Pallet {
+ /// Sets the xcm_dest_weight and fee for XCM operation of HomaXcm.
+ ///
+ /// Parameters:
+ /// - `updates`: tumple of (HomaXcmOperation, WeightChange, FeeChange).
+ #[pallet::weight(10_000)]
+ #[transactional]
+ pub fn update_xcm_dest_weight_and_fee(
+ origin: OriginFor,
+ updates: Vec<(HomaXcmOperation, Option, Option)>,
+ ) -> DispatchResult {
+ T::UpdateOrigin::ensure_origin(origin)?;
+
+ for (operation, weight_change, fee_change) in updates {
+ XcmDestWeightAndFee::::mutate(operation, |(weight, fee)| {
+ if let Some(new_weight) = weight_change {
+ *weight = new_weight;
+ Self::deposit_event(Event::::XcmDestWeightUpdated(operation, new_weight));
+ }
+ if let Some(new_fee) = fee_change {
+ *fee = new_fee;
+ Self::deposit_event(Event::::XcmFeeUpdated(operation, new_fee));
+ }
+ });
+ }
+
+ Ok(())
+ }
+ }
+
+ impl Pallet {}
+
+ impl HomaSubAccountXcm for Pallet {
+ /// Cross-chain transfer staking currency to sub account on relaychain.
+ fn transfer_staking_to_sub_account(
+ sender: &T::AccountId,
+ sub_account_index: u16,
+ amount: Balance,
+ ) -> DispatchResult {
+ T::XcmTransfer::transfer(
+ sender.clone(),
+ T::StakingCurrencyId::get(),
+ amount,
+ T::SovereignSubAccountLocationConvert::convert(sub_account_index),
+ Self::xcm_dest_weight_and_fee(HomaXcmOperation::XtokensTransfer).0,
+ )
+ }
+
+ /// Send XCM message to the relaychain for sub account to withdraw_unbonded staking currency
+ /// and send it back.
+ fn withdraw_unbonded_from_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult {
+ let (xcm_dest_weight, xcm_fee) = Self::xcm_dest_weight_and_fee(HomaXcmOperation::XcmWithdrawUnbonded);
+ let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message(
+ T::RelayChainCallBuilder::utility_as_derivative_call(
+ T::RelayChainCallBuilder::utility_batch_call(vec![
+ T::RelayChainCallBuilder::staking_withdraw_unbonded(T::RelayChainUnbondingSlashingSpans::get()),
+ T::RelayChainCallBuilder::balances_transfer_keep_alive(T::ParachainAccount::get(), amount),
+ ]),
+ sub_account_index,
+ ),
+ xcm_fee,
+ xcm_dest_weight,
+ );
+ let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message);
+ log::debug!(
+ target: "homa-xcm",
+ "subaccount {:?} send XCM to withdraw unbonded {:?}, result: {:?}",
+ sub_account_index, amount, result
+ );
+
+ ensure!(result.is_ok(), Error::::XcmFailed);
+ Ok(())
+ }
+
+ /// Send XCM message to the relaychain for sub account to bond extra.
+ fn bond_extra_on_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult {
+ let (xcm_dest_weight, xcm_fee) = Self::xcm_dest_weight_and_fee(HomaXcmOperation::XcmBondExtra);
+ let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message(
+ T::RelayChainCallBuilder::utility_as_derivative_call(
+ T::RelayChainCallBuilder::staking_bond_extra(amount),
+ sub_account_index,
+ ),
+ xcm_fee,
+ xcm_dest_weight,
+ );
+ let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message);
+ log::debug!(
+ target: "homa-xcm",
+ "subaccount {:?} send XCM to bond {:?}, result: {:?}",
+ sub_account_index, amount, result,
+ );
+
+ ensure!(result.is_ok(), Error::::XcmFailed);
+ Ok(())
+ }
+
+ /// Send XCM message to the relaychain for sub account to unbond.
+ fn unbond_on_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult {
+ let (xcm_dest_weight, xcm_fee) = Self::xcm_dest_weight_and_fee(HomaXcmOperation::XcmUnbond);
+ let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message(
+ T::RelayChainCallBuilder::utility_as_derivative_call(
+ T::RelayChainCallBuilder::staking_unbond(amount),
+ sub_account_index,
+ ),
+ xcm_fee,
+ xcm_dest_weight,
+ );
+ let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message);
+ log::debug!(
+ target: "homa-xcm",
+ "subaccount {:?} send XCM to unbond {:?}, result: {:?}",
+ sub_account_index, amount, result
+ );
+
+ ensure!(result.is_ok(), Error::::XcmFailed);
+ Ok(())
+ }
+
+ /// The fee of cross-chain transfer is deducted from the recipient.
+ fn get_xcm_transfer_fee() -> Balance {
+ Self::xcm_dest_weight_and_fee(HomaXcmOperation::XtokensTransfer).1
+ }
+ }
+}
diff --git a/modules/homa/Cargo.toml b/modules/homa/Cargo.toml
index d037a56ee4..9fac1c4bac 100644
--- a/modules/homa/Cargo.toml
+++ b/modules/homa/Cargo.toml
@@ -10,13 +10,10 @@ scale-info = { version = "1.0", default-features = false, features = ["derive"]
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false, optional = true}
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
-sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
pallet-staking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
-pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12", default-features = false }
-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12", default-features = false }
primitives = { package = "acala-primitives", path = "../../primitives", default-features = false }
orml-traits = { path = "../../orml/traits", default-features = false }
module-support = { path = "../../modules/support", default-features = false }
@@ -26,9 +23,6 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" }
module-currencies = { path = "../../modules/currencies" }
orml-tokens = { path = "../../orml/tokens" }
-xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" }
-cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" }
-module-relaychain = { path = "../relaychain", features = ["kusama"] }
[features]
default = ["std"]
@@ -38,13 +32,10 @@ std = [
"frame-support/std",
"frame-system/std",
"scale-info/std",
- "sp-arithmetic/std",
"sp-runtime/std",
"sp-core/std",
"sp-std/std",
"pallet-staking/std",
- "pallet-xcm/std",
- "xcm/std",
"primitives/std",
"orml-traits/std",
"module-support/std",
@@ -53,6 +44,5 @@ runtime-benchmarks = [
"frame-benchmarking",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
- "pallet-xcm/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]
diff --git a/modules/homa/src/lib.rs b/modules/homa/src/lib.rs
index 01c2bb25df..e3baa7949e 100644
--- a/modules/homa/src/lib.rs
+++ b/modules/homa/src/lib.rs
@@ -21,27 +21,18 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::unused_unit)]
-use frame_support::{log, pallet_prelude::*, transactional, weights::Weight, BoundedVec, PalletId};
+use frame_support::{log, pallet_prelude::*, transactional, PalletId};
use frame_system::{ensure_signed, pallet_prelude::*};
-use module_support::{CallBuilder, ExchangeRate, ExchangeRateProvider, Rate};
-use orml_traits::{arithmetic::Signed, BalanceStatus, MultiCurrency, MultiCurrencyExtended, XcmTransfer};
+use module_support::{ExchangeRate, ExchangeRateProvider, HomaSubAccountXcm, Rate};
+use orml_traits::MultiCurrency;
use pallet_staking::EraIndex;
use primitives::{Balance, CurrencyId};
use scale_info::TypeInfo;
-use sp_arithmetic::traits::CheckedRem;
use sp_runtime::{
- traits::{AccountIdConversion, BlockNumberProvider, Bounded, Convert, One, Saturating, Zero},
- ArithmeticError, FixedPointNumber, Permill,
+ traits::{AccountIdConversion, Bounded, One, Saturating, Zero},
+ ArithmeticError, FixedPointNumber,
};
-use sp_std::{
- cmp::{min, Ordering},
- convert::{From, TryFrom, TryInto},
- ops::Mul,
- prelude::*,
- vec,
- vec::Vec,
-};
-use xcm::latest::prelude::*;
+use sp_std::{cmp::Ordering, convert::From, prelude::*, vec, vec::Vec};
pub use module::*;
@@ -101,14 +92,12 @@ pub mod module {
}
}
- pub type SubAccountIndex = u16;
-
#[pallet::config]
- pub trait Config: frame_system::Config + pallet_xcm::Config {
+ pub trait Config: frame_system::Config {
type Event: From> + IsType<::Event>;
/// Multi-currency support for asset management
- type Currency: MultiCurrencyExtended;
+ type Currency: MultiCurrency;
/// Origin represented Governance
type GovernanceOrigin: EnsureOrigin<::Origin>;
@@ -129,61 +118,21 @@ pub mod module {
#[pallet::constant]
type DefaultExchangeRate: Get;
- /// The threshold for mint operation in staking currency.
+ /// Vault reward of Homa protocol
#[pallet::constant]
- type MintThreshold: Get