diff --git a/Cargo.lock b/Cargo.lock index 43a7329d7..e101655fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3293,9 +3293,12 @@ name = "darwinia-staking" version = "6.6.0" dependencies = [ "darwinia-deposit", + "darwinia-message-transact", "darwinia-staking-traits", "dc-inflation", "dc-types", + "ethabi", + "ethereum", "frame-benchmarking", "frame-support", "frame-system", @@ -3892,19 +3895,6 @@ dependencies = [ "syn 2.0.38", ] -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.9.3" @@ -3912,7 +3902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", - "humantime 2.1.0", + "humantime", "log", "regex", "termcolor", @@ -3924,7 +3914,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "humantime 2.1.0", + "humantime", "is-terminal", "log", "regex", @@ -5489,15 +5479,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - [[package]] name = "humantime" version = "2.1.0" @@ -11199,11 +11180,11 @@ dependencies = [ [[package]] name = "pretty_env_logger" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ - "env_logger 0.7.1", + "env_logger 0.10.0", "log", ] diff --git a/pallet/account-migration/src/mock.rs b/pallet/account-migration/src/mock.rs index 59befed95..b3c715aaf 100644 --- a/pallet/account-migration/src/mock.rs +++ b/pallet/account-migration/src/mock.rs @@ -164,8 +164,11 @@ impl darwinia_staking::Config for Runtime { type Deposit = Deposit; type IssuingManager = (); type Kton = Dummy; + type KtonRewardDistributionContract = (); + type KtonStakerNotifier = (); type MaxDeposits = (); type MaxUnstakings = (); + type MigrationCurve = (); type MinStakingDuration = (); type Ring = Dummy; type RuntimeEvent = RuntimeEvent; diff --git a/pallet/message-transact/src/mock.rs b/pallet/message-transact/src/mock.rs index a08fae818..4f3a4dc7e 100644 --- a/pallet/message-transact/src/mock.rs +++ b/pallet/message-transact/src/mock.rs @@ -95,14 +95,12 @@ frame_support::parameter_types! { pub const BlockGasLimit: sp_core::U256 = sp_core::U256::MAX; pub const WeightPerGas: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(20_000, 0); } - pub struct FixedGasPrice; impl fp_evm::FeeCalculator for FixedGasPrice { fn min_gas_price() -> (sp_core::U256, frame_support::weights::Weight) { (sp_core::U256::from(5), frame_support::weights::Weight::zero()) } } - impl pallet_evm::Config for Runtime { type AddressMapping = pallet_evm::IdentityAddressMapping; type BlockGasLimit = BlockGasLimit; @@ -129,7 +127,6 @@ impl pallet_evm::Config for Runtime { frame_support::parameter_types! { pub const PostBlockAndTxnHashes: pallet_ethereum::PostLogContent = pallet_ethereum::PostLogContent::BlockAndTxnHashes; } - impl pallet_ethereum::Config for Runtime { type ExtraDataLength = (); type PostLogContent = PostBlockAndTxnHashes; diff --git a/pallet/staking/Cargo.toml b/pallet/staking/Cargo.toml index 6169da550..3033fc614 100644 --- a/pallet/staking/Cargo.toml +++ b/pallet/staking/Cargo.toml @@ -9,12 +9,15 @@ version.workspace = true [dependencies] # crates.io codec = { workspace = true, package = "parity-scale-codec" } +ethabi = { version = "18.0", default-features = false } +ethereum = { workspace = true } log = { workspace = true } scale-info = { workspace = true } # darwinia -darwinia-staking-traits = { workspace = true } -dc-types = { workspace = true } +darwinia-message-transact = { workspace = true } +darwinia-staking-traits = { workspace = true } +dc-types = { workspace = true } # darwinia optional darwinia-deposit = { workspace = true, optional = true } @@ -23,6 +26,7 @@ frame-support = { workspace = true } frame-system = { workspace = true } pallet-authorship = { workspace = true } pallet-session = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } # substrate optional @@ -30,7 +34,7 @@ frame-benchmarking = { workspace = true, optional = true } [dev-dependencies] # crates.io -pretty_env_logger = { version = "0.4" } +pretty_env_logger = { version = "0.5" } # darwinia darwinia-deposit = { workspace = true, features = ["std"] } @@ -42,7 +46,6 @@ pallet-balances = { workspace = true, features = ["std"] } pallet-session = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true, features = ["std"] } pallet-treasury = { workspace = true, features = ["std"] } -sp-core = { workspace = true, features = ["std"] } sp-io = { workspace = true, features = ["std"] } substrate-test-utils = { workspace = true } @@ -51,10 +54,13 @@ default = ["std"] std = [ # crates.io "codec/std", + "ethabi/std", + "ethereum/std", "log/std", "scale-info/std", # darwinia + "darwinia-message-transact/std", "darwinia-staking-traits/std", # darwinia optional "darwinia-deposit?/std", @@ -65,6 +71,7 @@ std = [ "pallet-authorship/std", "pallet-balances/std", "pallet-session/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", # substrate optional @@ -74,6 +81,7 @@ std = [ runtime-benchmarks = [ # darwinia "darwinia-deposit", + "darwinia-message-transact/runtime-benchmarks", "darwinia-staking-traits/runtime-benchmarks", # substrate @@ -85,6 +93,8 @@ runtime-benchmarks = [ ] try-runtime = [ + # darwinia + "darwinia-message-transact/try-runtime", # substrate "frame-support/try-runtime", "frame-system/try-runtime", diff --git a/pallet/staking/src/benchmarking.rs b/pallet/staking/src/benchmarking.rs index 5bf7d945b..d1c31288d 100644 --- a/pallet/staking/src/benchmarking.rs +++ b/pallet/staking/src/benchmarking.rs @@ -106,7 +106,7 @@ mod benchmarks { // // The total number of deposit items has reached `Config::MaxUnstakings`. #[extrinsic_call] - _(RawOrigin::Signed(a), UNIT, UNIT, deposits); + _(RawOrigin::Signed(a), UNIT, deposits); } #[benchmark] diff --git a/pallet/staking/src/lib.rs b/pallet/staking/src/lib.rs index 4e4c1ca63..b12fb6ac4 100644 --- a/pallet/staking/src/lib.rs +++ b/pallet/staking/src/lib.rs @@ -47,15 +47,23 @@ pub use weights::WeightInfo; pub use darwinia_staking_traits::*; +// core +use core::mem; // crates.io use codec::FullCodec; +use ethabi::{Function, Param, ParamType, StateMutability, Token}; +use ethereum::{ + LegacyTransaction, TransactionAction, TransactionSignature, TransactionV2 as Transaction, +}; // darwinia +use darwinia_message_transact::LcmpEthOrigin; use dc_types::{Balance, Moment}; // substrate use frame_support::{ pallet_prelude::*, traits::Currency, DefaultNoBound, EqNoBound, PalletId, PartialEqNoBound, }; use frame_system::{pallet_prelude::*, RawOrigin}; +use sp_core::{H160, H256, U256}; use sp_runtime::{ traits::{AccountIdConversion, Convert, One, Zero}, Perbill, Perquintill, @@ -125,6 +133,9 @@ pub mod pallet { /// Inflation and reward manager. type IssuingManager: IssuingManager; + /// KTON staker notifier. + type KtonStakerNotifier: KtonStakerNotification; + /// Pass [`pallet_session::Config::ShouldEndSession`]'s result to here. type ShouldEndSession: Get; @@ -139,6 +150,14 @@ pub mod pallet { /// Maximum unstaking/unbonding count. #[pallet::constant] type MaxUnstakings: Get; + + #[pallet::constant] + /// The curve of migration. + type MigrationCurve: Get; + + /// The address of KTON reward distribution contract. + #[pallet::constant] + type KtonRewardDistributionContract: Get; } #[allow(missing_docs)] @@ -311,6 +330,11 @@ pub mod pallet { #[pallet::getter(fn elapsed_time)] pub type ElapsedTime = StorageValue<_, Moment, ValueQuery>; + /// Migration starting block. + #[pallet::storage] + #[pallet::getter(fn migration_start_block)] + pub type MigrationStartBlock = StorageValue<_, BlockNumberFor, ValueQuery>; + #[derive(DefaultNoBound)] #[pallet::genesis_config] pub struct GenesisConfig { @@ -351,6 +375,12 @@ pub mod pallet { pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + >::put(>::block_number()); + + T::DbWeight::get().reads_writes(0, 1) + } + fn on_initialize(_: BlockNumberFor) -> Weight { // There are already plenty of tasks to handle during the new session, // so refrain from assigning any additional ones here. @@ -451,16 +481,13 @@ pub mod pallet { if ring_amount != 0 { Self::unstake_token::>( &mut l.staked_ring, - &mut l.unstaking_ring, + Some(&mut l.unstaking_ring), ring_amount, )?; } if kton_amount != 0 { - Self::unstake_token::>( - &mut l.staked_kton, - &mut l.unstaking_kton, - kton_amount, - )?; + Self::unstake_token::>(&mut l.staked_kton, None, kton_amount)?; + ::Kton::unstake(&who, kton_amount)?; } for d in deposits { @@ -481,12 +508,11 @@ pub mod pallet { pub fn restake( origin: OriginFor, ring_amount: Balance, - kton_amount: Balance, deposits: Vec>, ) -> DispatchResult { let who = ensure_signed(origin)?; - if ring_amount == 0 && kton_amount == 0 && deposits.is_empty() { + if ring_amount == 0 && deposits.is_empty() { return Ok(()); } @@ -500,13 +526,6 @@ pub mod pallet { ring_amount, )?; } - if kton_amount != 0 { - Self::restake_token::>( - &mut l.staked_kton, - &mut l.unstaking_kton, - kton_amount, - )?; - } for d in deposits { Self::restake_deposit(&who, l, d)?; @@ -669,7 +688,7 @@ pub mod pallet { fn unstake_token

( staked: &mut Balance, - unstaking: &mut BoundedVec<(Balance, BlockNumberFor), T::MaxUnstakings>, + unstaking: Option<&mut BoundedVec<(Balance, BlockNumberFor), T::MaxUnstakings>>, amount: Balance, ) -> DispatchResult where @@ -679,12 +698,13 @@ pub mod pallet { .checked_sub(amount) .ok_or("[pallet::staking] `u128` must not be overflowed; qed")?; - unstaking - .try_push(( + if let Some(u) = unstaking { + u.try_push(( amount, >::block_number() + T::MinStakingDuration::get(), )) .map_err(|_| >::ExceedMaxUnstakings)?; + } Self::update_pool::

(false, amount)?; @@ -785,26 +805,22 @@ pub mod pallet { >::try_mutate(who, |l| { let l = l.as_mut().ok_or(>::NotStaker)?; let now = >::block_number(); - let claim = |u: &mut BoundedVec<_, _>, c: &mut Balance| { - u.retain(|(a, t)| { - if t <= &now { - *c += a; - - false - } else { - true - } - }); - }; let mut r_claimed = 0; - claim(&mut l.unstaking_ring, &mut r_claimed); - ::Ring::unstake(who, r_claimed)?; - - let mut k_claimed = 0; + l.unstaking_ring.retain(|(a, t)| { + if t <= &now { + r_claimed += a; - claim(&mut l.unstaking_kton, &mut k_claimed); - ::Kton::unstake(who, k_claimed)?; + false + } else { + true + } + }); + ::Ring::unstake(who, r_claimed)?; + ::Kton::unstake( + who, + mem::take(&mut l.unstaking_kton).into_iter().fold(0, |s, (a, _)| s + a), + )?; let mut d_claimed = Vec::new(); @@ -856,14 +872,24 @@ pub mod pallet { /// Calculate the power of the given account. #[cfg(any(feature = "runtime-benchmarks", test))] pub fn quick_power_of(who: &T::AccountId) -> Power { - Self::power_of(who, >::get(), >::get()) + Self::power_of( + who, + >::get(), + >::get(), + T::MigrationCurve::get(), + ) } /// Calculate the power of the given account. /// /// This is an optimized version of [`Self::quick_power_of`]. /// Avoiding read the pools' storage multiple times. - pub fn power_of(who: &T::AccountId, ring_pool: Balance, kton_pool: Balance) -> Power { + pub fn power_of( + who: &T::AccountId, + ring_pool: Balance, + kton_pool: Balance, + migration_ratio: Perquintill, + ) -> Power { // Power is a mixture of RING and KTON. // - `total_ring_power = (amount / total_staked_ring) * HALF_POWER` // - `total_kton_power = (amount / total_staked_kton) * HALF_POWER` @@ -883,42 +909,47 @@ pub mod pallet { ) * HALF_POWER) .saturating_add( Perquintill::from_rational(l.staked_kton, kton_pool.max(1)) - * HALF_POWER, + * (migration_ratio * HALF_POWER), ) as _ }) .unwrap_or_default() } /// Distribute the session reward to staking pot and update the stakers' reward record. - pub fn distribute_session_reward(amount: Balance) -> Balance { + pub fn distribute_session_reward(amount: Balance) { + let (reward_to_v1, reward_to_v2) = { + let reward_to_ring = amount / 2; + let reward_to_kton = amount - reward_to_ring; + #[cfg(not(any(test, feature = "runtime-benchmarks")))] + let ratio = T::MigrationCurve::get(); + #[cfg(any(test, feature = "runtime-benchmarks"))] + let ratio = Perquintill::one(); + let reward_to_kton_v1 = ratio * reward_to_kton; + let reward_to_kton_v2 = reward_to_kton - reward_to_kton_v1; + + (reward_to_ring + reward_to_kton_v1, reward_to_kton_v2) + }; let (sum, map) = >::take(); let staking_pot = account_id(); - let mut actual_reward = 0; - let mut unpaid = 0; + let actual_reward_v1 = map.into_iter().fold(0, |s, (c, p)| { + let r = Perbill::from_rational(p, sum) * reward_to_v1; - map.into_iter().for_each(|(c, p)| { - let r = Perbill::from_rational(p, sum) * amount; + >::mutate(c, |u| *u = u.map(|u| u + r).or(Some(r))); - >::mutate(&c, |u| *u = u.map(|u| u + r).or(Some(r))); - - // TODO: merge into one call - if T::IssuingManager::reward(&staking_pot, r).is_ok() { - actual_reward += r; + s + r + }); + let reward = |who, amount| { + if T::IssuingManager::reward(&who, amount).is_ok() { + Self::deposit_event(Event::Payout { staker: who, amount }); } else { - unpaid += r; + Self::deposit_event(Event::Unpaid { staker: who, amount }); } - }); - - Self::deposit_event(Event::Payout { - staker: staking_pot.clone(), - amount: actual_reward, - }); + }; - if unpaid != 0 { - Self::deposit_event(Event::Unpaid { staker: staking_pot, amount: unpaid }); - } + reward(staking_pot, actual_reward_v1); + reward(T::KtonRewardDistributionContract::get(), reward_to_v2); - actual_reward + T::KtonStakerNotifier::notify(reward_to_v2); } /// Pay the reward to the collator and its nominators. @@ -1009,6 +1040,7 @@ pub mod pallet { let nominators = >::iter().collect::>(); let ring_pool = >::get(); let kton_pool = >::get(); + let migration_ratio = T::MigrationCurve::get(); let mut collators = >::iter() .map(|(c, cm)| { let scaler = Perbill::one() - cm; @@ -1017,7 +1049,8 @@ pub mod pallet { .iter() .filter_map(|(n, c_)| { if c_ == &c { - let nominator_v = scaler * Self::power_of(n, ring_pool, kton_pool); + let nominator_v = scaler + * Self::power_of(n, ring_pool, kton_pool, migration_ratio); collator_v += nominator_v; @@ -1064,11 +1097,8 @@ where fn on_session_end() { let inflation = Self::inflate(); let reward = Self::calculate_reward(inflation); - let actual_reward = >::distribute_session_reward(reward); - if inflation != 0 { - Self::clear(inflation.saturating_sub(actual_reward)); - } + >::distribute_session_reward(reward); } /// Inflation settings. @@ -1081,9 +1111,6 @@ where /// The reward function. fn reward(who: &T::AccountId, amount: Balance) -> DispatchResult; - - /// Clear the remaining inflation. - fn clear(_remaining: Balance) {} } impl IssuingManager for () where @@ -1153,7 +1180,7 @@ where /// A snapshot of the stake backing a single collator in the system. #[cfg_attr(test, derive(Clone))] -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] +#[derive(Encode, Decode, TypeInfo, RuntimeDebug)] pub struct Exposure { /// The commission of this collator. pub commission: Perbill, @@ -1164,7 +1191,7 @@ pub struct Exposure { } /// A snapshot of the staker's state. #[cfg_attr(test, derive(Clone))] -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] +#[derive(Encode, Decode, TypeInfo, RuntimeDebug)] pub struct IndividualExposure { /// Nominator. pub who: AccountId, @@ -1208,3 +1235,124 @@ where { PalletId(*b"da/staki").into_account_truncating() } + +/// The address of the RewardsDistribution. +/// 0x000000000Ae5DB7BDAf8D071e680452e33d91Dd5. +pub struct KtonRewardDistributionContract; +impl Get for KtonRewardDistributionContract +where + T: From<[u8; 20]>, +{ + fn get() -> T { + [0, 0, 0, 0, 10, 229, 219, 123, 218, 248, 208, 113, 230, 128, 69, 46, 51, 217, 29, 213] + .into() + } +} + +/// A curve helps to migrate to staking v2 smoothly. +pub struct MigrationCurve(PhantomData); +impl Get for MigrationCurve +where + T: Config, +{ + fn get() -> Perquintill { + // substrate + use sp_runtime::traits::SaturatedConversion; + + let x = (>::block_number() - >::get()) + .saturated_into::() + .max(1); + let month_in_blocks = 30 * 24 * 60 * 60 / 12; + + Perquintill::one() - Perquintill::from_rational(x, month_in_blocks) + } +} + +/// KTON staker contact notification interface. +pub trait KtonStakerNotification { + /// Notify the KTON staker contract. + fn notify(_: Balance) {} +} +impl KtonStakerNotification for () {} +/// KTON staker contact notifier. +pub struct KtonStakerNotifier(PhantomData); +impl KtonStakerNotification for KtonStakerNotifier +where + T: Config + darwinia_message_transact::Config, + T::RuntimeOrigin: Into> + From, + ::AccountId: Into, +{ + fn notify(amount: Balance) { + let Some(signature)= mock_sig() else { + log::error!("[pallet::staking] Invalid mock signature for the staking notify transaction."); + + return; + }; + // KTONStakingRewards + // 0x000000000419683a1a03AbC21FC9da25fd2B4dD7 + let staking_reward = + H160([0, 0, 0, 0, 4, 25, 104, 58, 26, 3, 171, 194, 31, 201, 218, 37, 253, 43, 77, 215]); + + let reward_distr = T::KtonRewardDistributionContract::get().into(); + // https://github.com/darwinia-network/kton-staker/blob/175f0ec131d4aef3bf64cfb2fce1d262e7ce9140/src/RewardsDistribution.sol#L11 + #[allow(deprecated)] + let function = Function { + name: "distributeRewards".into(), + inputs: vec![ + Param { + name: "ktonStakingRewards".into(), + kind: ParamType::Address, + internal_type: None, + }, + Param { name: "reward".into(), kind: ParamType::Uint(256), internal_type: None }, + ], + outputs: vec![Param { + name: "success or not".into(), + kind: ParamType::Bool, + internal_type: None, + }], + constant: None, + state_mutability: StateMutability::Payable, + }; + + let notify_transaction = LegacyTransaction { + nonce: U256::zero(), // Will be reset in the message transact call + gas_price: U256::zero(), // Will be reset in the message transact call + gas_limit: U256::from(1_000_000), /* It should be big enough for the evm + * transaction, otherwise it will out of gas. */ + action: TransactionAction::Call(reward_distr), + value: U256::zero(), + input: function + .encode_input(&[Token::Address(staking_reward), Token::Uint(amount.into())]) + .unwrap_or_default(), + signature, + }; + // b"sc/ktstk" + let sender = + H160([115, 99, 47, 107, 116, 115, 116, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + if let Err(e) = >::message_transact( + LcmpEthOrigin::MessageTransact(sender).into(), + Box::new(Transaction::Legacy(notify_transaction)), + ) { + log::error!("[pallet::staking] failed to notify KTON staker contract due to {e:?}"); + } + } +} +/// Mock a valid signature, copied from: +/// https://github.com/rust-ethereum/ethereum/blob/master/src/transaction/mod.rs#L230 +pub fn mock_sig() -> Option { + TransactionSignature::new( + 38, + // be67e0a07db67da8d446f76add590e54b6e92cb6b8f9835aeb67540579a27717 + H256([ + 190, 103, 224, 160, 125, 182, 125, 168, 212, 70, 247, 106, 221, 89, 14, 84, 182, 233, + 44, 182, 184, 249, 131, 90, 235, 103, 84, 5, 121, 162, 119, 23, + ]), + // 2d690516512020171c1ec870f6ff45398cc8609250326be89915fb538e7bd718 + H256([ + 45, 105, 5, 22, 81, 32, 32, 23, 28, 30, 200, 112, 246, 255, 69, 57, 140, 200, 96, 146, + 80, 50, 107, 232, 153, 21, 251, 83, 142, 123, 215, 24, + ]), + ) +} diff --git a/pallet/staking/src/mock.rs b/pallet/staking/src/mock.rs index 8c16c2943..4c39af409 100644 --- a/pallet/staking/src/mock.rs +++ b/pallet/staking/src/mock.rs @@ -232,6 +232,7 @@ impl pallet_treasury::Config for Runtime { frame_support::parameter_types! { pub PayoutFraction: sp_runtime::Perbill = sp_runtime::Perbill::from_percent(40); + pub MigrationCurve: sp_runtime::Perquintill = sp_runtime::Perquintill::one(); pub static InflationType: u8 = 0; } pub enum KtonStaking {} @@ -282,14 +283,6 @@ impl darwinia_staking::IssuingManager for StatedOnSessionEnd { OnCrabSessionEnd::reward(who, amount) } } - - fn clear(remaining: Balance) { - if INFLATION_TYPE.with(|v| *v.borrow()) == 0 { - OnDarwiniaSessionEnd::clear(remaining) - } else { - OnCrabSessionEnd::clear(remaining) - } - } } pub enum OnDarwiniaSessionEnd {} impl darwinia_staking::IssuingManager for OnDarwiniaSessionEnd { @@ -316,10 +309,6 @@ impl darwinia_staking::IssuingManager for OnDarwiniaSessionEnd { Ok(()) } - - fn clear(remaining: Balance) { - let _ = Balances::deposit_into_existing(&Treasury::account_id(), remaining); - } } pub enum OnCrabSessionEnd {} impl darwinia_staking::IssuingManager for OnCrabSessionEnd { @@ -352,8 +341,11 @@ impl darwinia_staking::Config for Runtime { type Deposit = Deposit; type IssuingManager = StatedOnSessionEnd; type Kton = KtonStaking; + type KtonRewardDistributionContract = (); + type KtonStakerNotifier = (); type MaxDeposits = ::MaxDeposits; type MaxUnstakings = frame_support::traits::ConstU32<16>; + type MigrationCurve = MigrationCurve; type MinStakingDuration = frame_support::traits::ConstU64<3>; type Ring = RingStaking; type RuntimeEvent = RuntimeEvent; diff --git a/pallet/staking/src/tests.rs b/pallet/staking/src/tests.rs index a78154eed..fe5ddfb31 100644 --- a/pallet/staking/src/tests.rs +++ b/pallet/staking/src/tests.rs @@ -19,9 +19,9 @@ // core use core::time::Duration; // darwinia -use crate::{mock::*, *}; +use crate::{mock::*, MigrationCurve, *}; use darwinia_deposit::Error as DepositError; -use dc_types::{Balance, UNIT}; +use dc_types::UNIT; // substrate use frame_support::{assert_noop, assert_ok, BoundedVec}; use sp_runtime::{assert_eq_error_rate, DispatchError, Perbill}; @@ -219,7 +219,6 @@ fn unstake_should_work() { staked_kton: 2 * UNIT, staked_deposits: BoundedVec::truncate_from(vec![0, 1, 2]), unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 6)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 7)]), ..ZeroDefault::default() } ); @@ -240,8 +239,8 @@ fn unstake_should_work() { staked_kton: 2 * UNIT, staked_deposits: BoundedVec::truncate_from(vec![0, 2]), unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 6)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 7)]), - unstaking_deposits: BoundedVec::truncate_from(vec![(1, 8)]) + unstaking_deposits: BoundedVec::truncate_from(vec![(1, 8)]), + ..ZeroDefault::default() } ); @@ -252,7 +251,6 @@ fn unstake_should_work() { Staking::ledger_of(1).unwrap(), Ledger { unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 6), (2 * UNIT, 9)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 7), (2 * UNIT, 9)]), unstaking_deposits: BoundedVec::truncate_from(vec![(1, 8), (0, 9), (2, 9)]), ..ZeroDefault::default() } @@ -260,7 +258,7 @@ fn unstake_should_work() { // Keep the stakes for at least `MinStakingDuration`. assert_eq!(Balances::free_balance(1), 994 * UNIT); - assert_eq!(Assets::balance(0, 1), 997 * UNIT + 22_842_639_593_907); + assert_eq!(Assets::balance(0, 1), 1_000 * UNIT + 22_842_639_593_907); }); } @@ -277,39 +275,23 @@ fn restake_should_work() { Efflux::block(1); assert_ok!(Staking::unstake(RuntimeOrigin::signed(1), UNIT, UNIT, Vec::new())); assert_eq!(Balances::free_balance(1), 994 * UNIT); - assert_eq!(Assets::balance(0, 1), 997 * UNIT + 22_842_639_593_907); + assert_eq!(Assets::balance(0, 1), 1_000 * UNIT + 22_842_639_593_907); assert_eq!( Staking::ledger_of(1).unwrap(), Ledger { unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT, 7), (UNIT, 8)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT, 7), (UNIT, 8)]), unstaking_deposits: BoundedVec::truncate_from(vec![(0, 6), (1, 6), (2, 6)]), ..ZeroDefault::default() } ); // Restake 1.5 RING. - assert_ok!(Staking::restake(RuntimeOrigin::signed(1), 3 * UNIT / 2, 0, Vec::new())); - assert_eq!( - Staking::ledger_of(1).unwrap(), - Ledger { - staked_ring: 3 * UNIT / 2, - unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT / 2, 7)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT, 7), (UNIT, 8)]), - unstaking_deposits: BoundedVec::truncate_from(vec![(0, 6), (1, 6), (2, 6)]), - ..ZeroDefault::default() - } - ); - - // Restake 1.5 KTON. - assert_ok!(Staking::restake(RuntimeOrigin::signed(1), 0, 3 * UNIT / 2, Vec::new())); + assert_ok!(Staking::restake(RuntimeOrigin::signed(1), 3 * UNIT / 2, Vec::new())); assert_eq!( Staking::ledger_of(1).unwrap(), Ledger { staked_ring: 3 * UNIT / 2, - staked_kton: 3 * UNIT / 2, unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT / 2, 7)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT / 2, 7)]), unstaking_deposits: BoundedVec::truncate_from(vec![(0, 6), (1, 6), (2, 6)]), ..ZeroDefault::default() } @@ -322,32 +304,25 @@ fn restake_should_work() { ); // Restake 1 deposit. - assert_ok!(Staking::restake(RuntimeOrigin::signed(1), 0, 0, vec![1])); + assert_ok!(Staking::restake(RuntimeOrigin::signed(1), 0, vec![1])); assert_eq!( Staking::ledger_of(1).unwrap(), Ledger { staked_ring: 3 * UNIT / 2, - staked_kton: 3 * UNIT / 2, staked_deposits: BoundedVec::truncate_from(vec![1]), unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT / 2, 7)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT / 2, 7)]), unstaking_deposits: BoundedVec::truncate_from(vec![(0, 6), (2, 6)]), + ..ZeroDefault::default() } ); - // Restake 1.5 RING, 1.5 KTON and 2 deposits. + // Restake 1.5 RING and 2 deposits. Efflux::block(1); - assert_ok!(Staking::restake( - RuntimeOrigin::signed(1), - 3 * UNIT / 2, - 3 * UNIT / 2, - vec![0, 2] - )); + assert_ok!(Staking::restake(RuntimeOrigin::signed(1), 3 * UNIT / 2, vec![0, 2])); assert_eq!( Staking::ledger_of(1).unwrap(), Ledger { staked_ring: 3 * UNIT, - staked_kton: 3 * UNIT, staked_deposits: BoundedVec::truncate_from(vec![1, 0, 2]), ..ZeroDefault::default() } @@ -371,12 +346,11 @@ fn claim_should_work() { Efflux::block(1); assert_ok!(Staking::unstake(RuntimeOrigin::signed(1), UNIT, UNIT, vec![1, 2])); assert_eq!(Balances::free_balance(1), 995 * UNIT); - assert_eq!(Assets::balance(0, 1), 998 * UNIT + 22_842_639_593_907); + assert_eq!(Assets::balance(0, 1), 1_000 * UNIT + 22_842_639_593_907); assert_eq!( Staking::ledger_of(1).unwrap(), Ledger { unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 6), (UNIT, 9)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 7), (UNIT, 9)]), unstaking_deposits: BoundedVec::truncate_from(vec![(0, 8), (1, 9), (2, 9)]), ..ZeroDefault::default() } @@ -390,7 +364,6 @@ fn claim_should_work() { Staking::ledger_of(1).unwrap(), Ledger { unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 9)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 7), (UNIT, 9)]), unstaking_deposits: BoundedVec::truncate_from(vec![(0, 8), (1, 9), (2, 9)]), ..ZeroDefault::default() } @@ -400,12 +373,10 @@ fn claim_should_work() { Efflux::block(1); assert_ok!(Staking::claim(RuntimeOrigin::signed(1))); assert_eq!(System::account(1).consumers, 2); - assert_eq!(Assets::balance(0, 1), 999 * UNIT + 22_842_639_593_907); assert_eq!( Staking::ledger_of(1).unwrap(), Ledger { unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 9)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 9)]), unstaking_deposits: BoundedVec::truncate_from(vec![(0, 8), (1, 9), (2, 9)]), ..ZeroDefault::default() } @@ -415,12 +386,10 @@ fn claim_should_work() { Efflux::block(1); assert_ok!(Staking::claim(RuntimeOrigin::signed(1))); assert_eq!(System::account(1).consumers, 2); - assert_eq!(Assets::balance(0, 1), 999 * UNIT + 22_842_639_593_907); assert_eq!( Staking::ledger_of(1).unwrap(), Ledger { unstaking_ring: BoundedVec::truncate_from(vec![(UNIT, 9)]), - unstaking_kton: BoundedVec::truncate_from(vec![(UNIT, 9)]), unstaking_deposits: BoundedVec::truncate_from(vec![(1, 9), (2, 9)]), ..ZeroDefault::default() } @@ -431,7 +400,6 @@ fn claim_should_work() { assert_ok!(Staking::claim(RuntimeOrigin::signed(1))); assert_eq!(System::account(1).consumers, 1); assert_eq!(Balances::free_balance(1), 997 * UNIT); - assert_eq!(Assets::balance(0, 1), 1_000 * UNIT + 22_842_639_593_907); assert!(Staking::ledger_of(1).is_none()); }); } @@ -892,3 +860,38 @@ fn on_new_session_should_work() { ); }); } + +#[test] +fn migration_curves_should_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(10); + >::put(10); + + assert_eq!( + vec![0, 1, 7, 14, 21, 29, 30, 31, 999] + .into_iter() + .map(|x| { + System::set_block_number(10 + x * 24 * 60 * 60 / 12); + + format!("{:?}", >::get()) + }) + .collect::>(), + [ + "99.9995370370370371%", + "96.6666666666666667%", + "76.6666666666666667%", + "53.3333333333333334%", + "30%", + "3.3333333333333334%", + "0%", + "0%", + "0%" + ] + ); + }); +} + +#[test] +fn mock_sig_should_work() { + assert!(mock_sig().is_some()); +} diff --git a/precompile/metadata/abi/staking.json b/precompile/metadata/abi/staking.json index 1b40504d7..5c7f8a400 100644 --- a/precompile/metadata/abi/staking.json +++ b/precompile/metadata/abi/staking.json @@ -104,11 +104,6 @@ "name": "ringAmount", "type": "uint256" }, - { - "internalType": "uint256", - "name": "ktonAmount", - "type": "uint256" - }, { "internalType": "uint8[]", "name": "depositIds", @@ -247,13 +242,12 @@ "_0": "returns true on success, false otherwise." } }, - "restake(uint256,uint256,uint8[])": + "restake(uint256,uint8[])": { "details": "Re-stake the unstaking assets immediately.", "params": { "depositIds": "The deposit ids list", - "ktonAmount": "The amount of staking KTON asset", "ringAmount": "The amount of staking RING asset" }, "returns": @@ -300,7 +294,7 @@ "collect(uint32)": "10a66536", "nominate(address)": "b332180b", "payout(address)": "0b7e9c44", - "restake(uint256,uint256,uint8[])": "17092fcb", + "restake(uint256,uint8[])": "6dbcd550", "stake(uint256,uint256,uint8[])": "757f9b3b", "unstake(uint256,uint256,uint8[])": "ef20fcb3" } diff --git a/precompile/metadata/sol/staking.sol b/precompile/metadata/sol/staking.sol index 1ca95a180..6deec0a74 100644 --- a/precompile/metadata/sol/staking.sol +++ b/precompile/metadata/sol/staking.sol @@ -51,12 +51,10 @@ interface Staking { /// @dev Re-stake the unstaking assets immediately. /// @param ringAmount The amount of staking RING asset - /// @param ktonAmount The amount of staking KTON asset /// @param depositIds The deposit ids list /// @return true on success, false otherwise. function restake( uint256 ringAmount, - uint256 ktonAmount, uint8[] memory depositIds ) external returns (bool); diff --git a/precompile/staking/src/lib.rs b/precompile/staking/src/lib.rs index 5d655b6e4..8afc8b8ae 100644 --- a/precompile/staking/src/lib.rs +++ b/precompile/staking/src/lib.rs @@ -97,11 +97,10 @@ where Ok(true) } - #[precompile::public("restake(uint256,uint256,uint8[])")] + #[precompile::public("restake(uint256,uint8[])")] fn restake( handle: &mut impl PrecompileHandle, ring_amount: U256, - kton_amount: U256, deposits: Vec, ) -> EvmResult { let origin = handle.context().caller.into(); @@ -112,7 +111,6 @@ where Some(origin).into(), darwinia_staking::Call::::restake { ring_amount: ring_amount.as_u128(), - kton_amount: kton_amount.as_u128(), deposits, }, )?; diff --git a/precompile/staking/src/mock.rs b/precompile/staking/src/mock.rs index aba2a5b6c..cb94d8dc6 100644 --- a/precompile/staking/src/mock.rs +++ b/precompile/staking/src/mock.rs @@ -236,8 +236,11 @@ impl darwinia_staking::Config for Runtime { type Deposit = Deposit; type IssuingManager = (); type Kton = KtonStaking; + type KtonRewardDistributionContract = (); + type KtonStakerNotifier = (); type MaxDeposits = ::MaxDeposits; type MaxUnstakings = frame_support::traits::ConstU32<16>; + type MigrationCurve = (); type MinStakingDuration = frame_support::traits::ConstU64<3>; type Ring = RingStaking; type RuntimeEvent = RuntimeEvent; diff --git a/precompile/staking/src/tests.rs b/precompile/staking/src/tests.rs index 4b3d32e28..71aeb568a 100644 --- a/precompile/staking/src/tests.rs +++ b/precompile/staking/src/tests.rs @@ -35,7 +35,7 @@ fn precompiles() -> TestPrecompiles { fn selectors() { assert!(PCall::stake_selectors().contains(&0x757f9b3b)); assert!(PCall::unstake_selectors().contains(&0xef20fcb3)); - assert!(PCall::restake_selectors().contains(&0x17092fcb)); + assert!(PCall::restake_selectors().contains(&0x6dbcd550)); assert!(PCall::claim_selectors().contains(&0x4e71d92d)); assert!(PCall::nominate_selectors().contains(&0xb332180b)); assert!(PCall::collect_selectors().contains(&0x10a66536)); @@ -81,11 +81,7 @@ fn stake_unstake_restake() { .prepare_test( alice, Precompile, - PCall::restake { - ring_amount: 200.into(), - kton_amount: U256::zero(), - deposits: vec![], - }, + PCall::restake { ring_amount: 200.into(), deposits: vec![] }, ) .execute_returns(true); assert_eq!(Staking::ledger_of(alice).unwrap().staked_ring, 200); diff --git a/runtime/crab/src/pallets/staking.rs b/runtime/crab/src/pallets/staking.rs index 6a617aefa..c5855f1e2 100644 --- a/runtime/crab/src/pallets/staking.rs +++ b/runtime/crab/src/pallets/staking.rs @@ -105,8 +105,11 @@ impl darwinia_staking::Config for Runtime { type Deposit = Deposit; type IssuingManager = OnCrabSessionEnd; type Kton = KtonStaking; + type KtonRewardDistributionContract = darwinia_staking::KtonRewardDistributionContract; + type KtonStakerNotifier = darwinia_staking::KtonStakerNotifier; type MaxDeposits = ::MaxDeposits; type MaxUnstakings = ConstU32<16>; + type MigrationCurve = darwinia_staking::MigrationCurve; type MinStakingDuration = MinStakingDuration; type Ring = RingStaking; type RuntimeEvent = RuntimeEvent; diff --git a/runtime/darwinia/src/pallets/staking.rs b/runtime/darwinia/src/pallets/staking.rs index 785e19d53..fe427555a 100644 --- a/runtime/darwinia/src/pallets/staking.rs +++ b/runtime/darwinia/src/pallets/staking.rs @@ -97,10 +97,6 @@ impl darwinia_staking::IssuingManager for OnDarwiniaSessionEnd { Ok(()) } - - fn clear(remaining: Balance) { - let _ = Balances::deposit_into_existing(&Treasury::account_id(), remaining); - } } pub enum ShouldEndSession {} @@ -120,8 +116,11 @@ impl darwinia_staking::Config for Runtime { type Deposit = Deposit; type IssuingManager = OnDarwiniaSessionEnd; type Kton = KtonStaking; + type KtonRewardDistributionContract = darwinia_staking::KtonRewardDistributionContract; + type KtonStakerNotifier = darwinia_staking::KtonStakerNotifier; type MaxDeposits = ::MaxDeposits; type MaxUnstakings = ConstU32<16>; + type MigrationCurve = darwinia_staking::MigrationCurve; type MinStakingDuration = MinStakingDuration; type Ring = RingStaking; type RuntimeEvent = RuntimeEvent; diff --git a/runtime/pangolin/Cargo.toml b/runtime/pangolin/Cargo.toml index d93cdddc4..6553072b7 100644 --- a/runtime/pangolin/Cargo.toml +++ b/runtime/pangolin/Cargo.toml @@ -254,8 +254,8 @@ std = [ "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", "pallet-treasury/std", - "pallet-utility/std", "pallet-tx-pause/std", + "pallet-utility/std", "pallet-whitelist/std", "sp-api/std", "sp-block-builder/std", diff --git a/runtime/pangolin/src/pallets/staking.rs b/runtime/pangolin/src/pallets/staking.rs index bdc9a2bfd..f02b17f77 100644 --- a/runtime/pangolin/src/pallets/staking.rs +++ b/runtime/pangolin/src/pallets/staking.rs @@ -99,8 +99,11 @@ impl darwinia_staking::Config for Runtime { type Deposit = Deposit; type IssuingManager = OnPangolinSessionEnd; type Kton = KtonStaking; + type KtonRewardDistributionContract = darwinia_staking::KtonRewardDistributionContract; + type KtonStakerNotifier = darwinia_staking::KtonStakerNotifier; type MaxDeposits = ::MaxDeposits; type MaxUnstakings = ConstU32<16>; + type MigrationCurve = darwinia_staking::MigrationCurve; type MinStakingDuration = ConstU32<{ 2 * MINUTES }>; type Ring = RingStaking; type RuntimeEvent = RuntimeEvent; diff --git a/runtime/pangoro/src/pallets/staking.rs b/runtime/pangoro/src/pallets/staking.rs index 242dfc9c4..27f26cab3 100644 --- a/runtime/pangoro/src/pallets/staking.rs +++ b/runtime/pangoro/src/pallets/staking.rs @@ -93,10 +93,6 @@ impl darwinia_staking::IssuingManager for OnPangoroSessionEnd { Ok(()) } - - fn clear(remaining: Balance) { - let _ = Balances::deposit_into_existing(&Treasury::account_id(), remaining); - } } pub enum ShouldEndSession {} @@ -116,8 +112,11 @@ impl darwinia_staking::Config for Runtime { type Deposit = Deposit; type IssuingManager = OnPangoroSessionEnd; type Kton = KtonStaking; + type KtonRewardDistributionContract = darwinia_staking::KtonRewardDistributionContract; + type KtonStakerNotifier = darwinia_staking::KtonStakerNotifier; type MaxDeposits = ::MaxDeposits; type MaxUnstakings = ConstU32<16>; + type MigrationCurve = darwinia_staking::MigrationCurve; type MinStakingDuration = ConstU32<{ 10 * MINUTES }>; type Ring = RingStaking; type RuntimeEvent = RuntimeEvent;