From 45b53b3b23696cc2a51330c7ce99fc1bfbf1c1ee Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Thu, 15 Sep 2022 14:59:43 +0900 Subject: [PATCH 01/15] rebond unlocking chunks --- frame/dapps-staking/src/lib.rs | 45 ++++++++++++++ frame/dapps-staking/src/pallet/mod.rs | 76 ++++++++++++++++++++++++ frame/dapps-staking/src/testing_utils.rs | 66 ++++++++++++++++++++ frame/dapps-staking/src/tests.rs | 72 ++++++++++++++++++++++ frame/dapps-staking/src/tests_lib.rs | 18 ++++++ frame/dapps-staking/src/weights.rs | 9 +++ 6 files changed, 286 insertions(+) diff --git a/frame/dapps-staking/src/lib.rs b/frame/dapps-staking/src/lib.rs index 0016dc11..62fbb282 100644 --- a/frame/dapps-staking/src/lib.rs +++ b/frame/dapps-staking/src/lib.rs @@ -56,6 +56,7 @@ use sp_runtime::{ RuntimeDebug, }; use sp_std::{ops::Add, prelude::*}; +use core::cmp::Ordering; pub mod pallet; pub mod weights; @@ -408,6 +409,14 @@ pub struct UnbondingInfo { unlocking_chunks: Vec>, } +pub fn unlock_era_asc(a: &UnlockingChunk, b: &UnlockingChunk) -> Ordering { + a.unlock_era.cmp(&b.unlock_era) +} + +pub fn unlock_era_desc(a: &UnlockingChunk, b: &UnlockingChunk) -> Ordering { + b.unlock_era.cmp(&a.unlock_era) +} + impl UnbondingInfo where Balance: AtLeast32BitUnsigned + Default + Copy, @@ -445,6 +454,15 @@ where } } + fn sort(&mut self, compare: F) + where + F: FnMut(&UnlockingChunk, &UnlockingChunk) -> Ordering + { + self + .unlocking_chunks + .sort_by(compare); + } + /// Partitions the unlocking chunks into two groups: /// /// First group includes all chunks which have unlock era lesser or equal to the specified era. @@ -470,6 +488,33 @@ where ) } + fn collect_amount(self, amount: Balance) -> (Balance, Self) { + let mut remaining_chunks: Vec> = Default::default(); + let collected_amount = self + .unlocking_chunks + .iter() + .fold(Balance::zero(), |accum, item| { + let next_accum = accum + item.amount; + if next_accum <= amount { + return next_accum; + } + + if accum < amount && amount < next_accum { + let excessive_amount = next_accum - amount; + remaining_chunks.push(UnlockingChunk{ + amount: excessive_amount, + unlock_era: item.unlock_era, + }); + return amount; + } + + remaining_chunks.push(*item); + accum + }); + + (collected_amount, Self { unlocking_chunks: remaining_chunks }) + } + #[cfg(test)] /// Return clone of the internal vector. Should only be used for testing. fn vec(&self) -> Vec> { diff --git a/frame/dapps-staking/src/pallet/mod.rs b/frame/dapps-staking/src/pallet/mod.rs index 974a2602..25b995ad 100644 --- a/frame/dapps-staking/src/pallet/mod.rs +++ b/frame/dapps-staking/src/pallet/mod.rs @@ -218,6 +218,8 @@ pub mod pallet { BalanceOf, T::SmartContract, ), + /// Account has rebonded unlocking chunks and staked funds on a smart contract. + RebondAndStake(T::AccountId, T::SmartContract, BalanceOf) } #[pallet::error] @@ -270,6 +272,8 @@ pub mod pallet { NotActiveStaker, /// Transfering nomination to the same contract NominationTransferToSameContract, + /// There are no previously unbonded funds that can be rebonded and re-staked. + NothingToRebond, } #[pallet::hooks] @@ -570,6 +574,78 @@ pub mod pallet { Ok(().into()) } + /// Lock up and stake unbonded chunks of origin account. + /// + /// `value` must be more than the `minimum_balance` specified by `MinimumStakingAmount` + /// unless account already has bonded value equal or more than 'minimum_balance'. + /// + /// The dispatch origin for this call must be _Signed_ by the staker's account. + #[pallet::weight(T::WeightInfo::rebond_and_stake())] + pub fn rebond_and_stake( + origin: OriginFor, + contract_id: T::SmartContract, + #[pallet::compact] value: BalanceOf, + ) -> DispatchResultWithPostInfo { + Self::ensure_pallet_enabled()?; + let staker = ensure_signed(origin)?; + + // Check that contract is ready for staking. + ensure!( + Self::is_active(&contract_id), + Error::::NotOperatedContract + ); + + // Get the staking ledger or create an entry if it doesn't exist. + let mut ledger = Self::ledger(&staker); + ensure!( + !ledger.unbonding_info.is_empty(), + Error::::NothingToRebond + ); + + // Sort chunks descending order by unlock_era, so that chunks with bigger era_index are collected with priority. + ledger.unbonding_info.sort(unlock_era_desc); + let (value_to_stake, mut remaining_chunks) = ledger.unbonding_info.collect_amount(value); + ensure!( + value_to_stake > Zero::zero(), + Error::::StakingWithNoValue + ); + + let current_era = Self::current_era(); + let mut staking_info = + Self::contract_stake_info(&contract_id, current_era).unwrap_or_default(); + let mut staker_info = Self::staker_info(&staker, &contract_id); + + Self::stake_on_contract( + &mut staker_info, + &mut staking_info, + value_to_stake, + current_era, + )?; + + remaining_chunks.sort(unlock_era_asc); + ledger.unbonding_info = remaining_chunks; + ledger.locked = ledger.locked.saturating_add(value_to_stake); + + GeneralEraInfo::::mutate(¤t_era, |value| { + if let Some(x) = value { + x.staked = x.staked.saturating_add(value_to_stake); + x.locked = x.locked.saturating_add(value_to_stake); + } + }); + + Self::update_ledger(&staker, ledger); + Self::update_staker_info(&staker, &contract_id, staker_info); + ContractEraStake::::insert(&contract_id, current_era, staking_info); + + Self::deposit_event(Event::::RebondAndStake( + staker, + contract_id, + value_to_stake, + )); + + Ok(().into()) + } + /// Withdraw all funds that have completed the unbonding process. /// /// If there are unbonding chunks which will be fully unbonded in future eras, diff --git a/frame/dapps-staking/src/testing_utils.rs b/frame/dapps-staking/src/testing_utils.rs index 953b2698..d8a2a5f5 100644 --- a/frame/dapps-staking/src/testing_utils.rs +++ b/frame/dapps-staking/src/testing_utils.rs @@ -353,6 +353,72 @@ pub(crate) fn assert_unbond_and_unstake( assert_eq!(init_state.era_info.locked, final_state.era_info.locked); } +pub(crate) fn assert_rebond_and_stake( + staker: AccountId, + contract_id: &MockSmartContract, + value: Balance, +) { + // Get latest staking info + let current_era = DappsStaking::current_era(); + let init_state = MemorySnapshot::all(current_era, &contract_id, staker); + let mut init_ledger = init_state.ledger.clone(); + + // Define expected state after extrinsic + init_ledger.unbonding_info.sort(unlock_era_desc); + let (expected_stake_amount, mut expected_remaining_info) = init_ledger.unbonding_info.collect_amount(value); + expected_remaining_info.sort(unlock_era_asc); + + // Ensure op is successful and event is emitted + assert_ok!(DappsStaking::rebond_and_stake(Origin::signed(staker), contract_id.clone(), value)); + System::assert_last_event(mock::Event::DappsStaking(Event::RebondAndStake( + staker, + contract_id.clone(), + expected_stake_amount, + ))); + + // Fetch the latest unbonding info so we can compare it to initial unbonding info + let final_state = MemorySnapshot::all(current_era, &contract_id, staker); + let final_ledger = final_state.ledger.clone(); + assert_eq!( + final_ledger.unbonding_info, + expected_remaining_info, + ); + assert_eq!( + final_ledger.locked, + init_ledger.locked + expected_stake_amount + ); + + // In case staker hasn't been staking this contract until now + if init_state.staker_info.latest_staked_value() == 0 { + assert!(GeneralStakerInfo::::contains_key( + &staker, + contract_id + )); + assert_eq!( + final_state.contract_info.number_of_stakers, + init_state.contract_info.number_of_stakers + 1 + ); + } + + // Verify the remaining states + assert_eq!( + final_state.era_info.staked, + init_state.era_info.staked + expected_stake_amount + ); + assert_eq!( + final_state.era_info.locked, + init_state.era_info.locked + expected_stake_amount + ); + assert_eq!( + final_state.contract_info.total, + init_state.contract_info.total + expected_stake_amount + ); + assert_eq!( + final_state.staker_info.latest_staked_value(), + init_state.staker_info.latest_staked_value() + expected_stake_amount + ); +} + /// Used to perform start_unbonding with success and storage assertions. pub(crate) fn assert_withdraw_unbonded(staker: AccountId) { let current_era = DappsStaking::current_era(); diff --git a/frame/dapps-staking/src/tests.rs b/frame/dapps-staking/src/tests.rs index 27da2596..f8565890 100644 --- a/frame/dapps-staking/src/tests.rs +++ b/frame/dapps-staking/src/tests.rs @@ -1059,6 +1059,78 @@ fn unbond_and_unstake_with_no_chunks_allowed() { }) } +#[test] +fn rebond_and_stake_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + assert_register(10, &contract_id); + + let staker_id = 1; + assert_bond_and_stake(staker_id, &contract_id, 1000); + + let first_unbond_value = 100; + let second_unbond_value = 250; + let initial_era = DappsStaking::current_era(); + + // Unbond some amount in the initial era + assert_unbond_and_unstake(staker_id, &contract_id, first_unbond_value); + + // Advance one era and then unbond some more + advance_to_era(initial_era + 1); + assert_unbond_and_unstake(staker_id, &contract_id, second_unbond_value); + + // unbond and stake + assert_rebond_and_stake(staker_id, &contract_id, 300); + + // unbond and stake again. + // this time value exceeds the total amount of unbonding chunks, but it succeeds by consuming the total amount. + assert_rebond_and_stake(staker_id, &contract_id, 200); + + assert!(Ledger::::get(&staker_id) + .unbonding_info + .is_empty() + ); + }) +} + +#[test] +fn rebond_and_stake_unexist_contract_fails() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker_id = 1; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + assert_register(10, &contract_id); + + assert_bond_and_stake(staker_id, &contract_id, 1000); + assert_unbond_and_unstake(staker_id, &contract_id, 100); + + let non_exist_contract_id = MockSmartContract::Evm(H160::repeat_byte(0x02)); + assert_noop!( + DappsStaking::rebond_and_stake(Origin::signed(staker_id), non_exist_contract_id, 200), + Error::::NotOperatedContract, + ); + }) +} + +#[test] +fn rebond_and_stake_no_unbonding_chunks_fails() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker_id = 1; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + assert_register(10, &contract_id); + + assert_noop!( + DappsStaking::rebond_and_stake(Origin::signed(staker_id), contract_id, 200), + Error::::NothingToRebond, + ); + }) +} + #[test] fn withdraw_unbonded_is_ok() { ExternalityBuilder::build().execute_with(|| { diff --git a/frame/dapps-staking/src/tests_lib.rs b/frame/dapps-staking/src/tests_lib.rs index 8062265f..9f991f5e 100644 --- a/frame/dapps-staking/src/tests_lib.rs +++ b/frame/dapps-staking/src/tests_lib.rs @@ -1,4 +1,5 @@ use super::*; +use alloc::vec; use frame_support::assert_ok; use mock::Balance; @@ -57,6 +58,23 @@ fn unbonding_info_test() { assert_eq!(unbonding_info.sum(), first_info.sum() + second_info.sum()); } +// #[test] +// fn unbonding_info_sort_test() { + // let mut unbonding_info = UnbondingInfo::::default(); + + // // Prepare unlocking chunks. + // let count = 5; + // let base_amount: Balance = 100; + // let base_unlock_era = 4 * count; + // let mut chunks = vec![]; + // for x in 1_u32..=count as u32 { + // chunks.push(UnlockingChunk { + // amount: base_amount * x as Balance, + // unlock_era: base_unlock_era - 3 * x, + // }); + // } +// } + #[test] fn staker_info_basic() { let staker_info = StakerInfo::::default(); diff --git a/frame/dapps-staking/src/weights.rs b/frame/dapps-staking/src/weights.rs index 15338833..0e5098e8 100644 --- a/frame/dapps-staking/src/weights.rs +++ b/frame/dapps-staking/src/weights.rs @@ -14,6 +14,7 @@ pub trait WeightInfo { fn developer_pre_approval() -> Weight; fn bond_and_stake() -> Weight; fn unbond_and_unstake() -> Weight; + fn rebond_and_stake() -> Weight; fn withdraw_unbonded() -> Weight; fn claim_staker_without_restake() -> Weight; fn claim_staker_with_restake() -> Weight; @@ -97,6 +98,10 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } + // TODO benchmarking + fn rebond_and_stake() -> Weight { + todo!() + } // Storage: DappsStaking PalletDisabled (r:1 w:0) // Storage: DappsStaking Ledger (r:1 w:1) // Storage: DappsStaking CurrentEra (r:1 w:0) @@ -248,6 +253,10 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } + // TODO benchmarking + fn rebond_and_stake() -> Weight { + todo!() + } // Storage: DappsStaking PalletDisabled (r:1 w:0) // Storage: DappsStaking Ledger (r:1 w:1) // Storage: DappsStaking CurrentEra (r:1 w:0) From 04a6b1f3b455800fe0ccbccc52cd5aee7e4ebbcf Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Thu, 15 Sep 2022 19:51:39 +0900 Subject: [PATCH 02/15] up --- frame/dapps-staking/src/lib.rs | 67 ++++++++------ frame/dapps-staking/src/pallet/mod.rs | 11 ++- frame/dapps-staking/src/testing_utils.rs | 14 +-- frame/dapps-staking/src/tests.rs | 5 +- frame/dapps-staking/src/tests_lib.rs | 111 +++++++++++++++++++---- 5 files changed, 148 insertions(+), 60 deletions(-) diff --git a/frame/dapps-staking/src/lib.rs b/frame/dapps-staking/src/lib.rs index 62fbb282..71eadebc 100644 --- a/frame/dapps-staking/src/lib.rs +++ b/frame/dapps-staking/src/lib.rs @@ -48,6 +48,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode, HasCompact}; +use core::cmp::Ordering; use frame_support::traits::Currency; use frame_system::{self as system}; use scale_info::TypeInfo; @@ -56,7 +57,6 @@ use sp_runtime::{ RuntimeDebug, }; use sp_std::{ops::Add, prelude::*}; -use core::cmp::Ordering; pub mod pallet; pub mod weights; @@ -409,11 +409,17 @@ pub struct UnbondingInfo { unlocking_chunks: Vec>, } -pub fn unlock_era_asc(a: &UnlockingChunk, b: &UnlockingChunk) -> Ordering { +pub fn unlock_era_asc( + a: &UnlockingChunk, + b: &UnlockingChunk, +) -> Ordering { a.unlock_era.cmp(&b.unlock_era) } -pub fn unlock_era_desc(a: &UnlockingChunk, b: &UnlockingChunk) -> Ordering { +pub fn unlock_era_desc( + a: &UnlockingChunk, + b: &UnlockingChunk, +) -> Ordering { b.unlock_era.cmp(&a.unlock_era) } @@ -456,11 +462,9 @@ where fn sort(&mut self, compare: F) where - F: FnMut(&UnlockingChunk, &UnlockingChunk) -> Ordering + F: FnMut(&UnlockingChunk, &UnlockingChunk) -> Ordering, { - self - .unlocking_chunks - .sort_by(compare); + self.unlocking_chunks.sort_by(compare); } /// Partitions the unlocking chunks into two groups: @@ -490,29 +494,34 @@ where fn collect_amount(self, amount: Balance) -> (Balance, Self) { let mut remaining_chunks: Vec> = Default::default(); - let collected_amount = self - .unlocking_chunks - .iter() - .fold(Balance::zero(), |accum, item| { - let next_accum = accum + item.amount; - if next_accum <= amount { - return next_accum; - } + let collected_amount = + self.unlocking_chunks + .iter() + .fold(Balance::zero(), |collected, item| { + let next_collected = collected + item.amount; + if next_collected <= amount { + return next_collected; + } + + if collected < amount && amount < next_collected { + let excessive_amount = next_collected - amount; + remaining_chunks.push(UnlockingChunk { + amount: excessive_amount, + unlock_era: item.unlock_era, + }); + return amount; + } + + remaining_chunks.push(*item); + collected + }); - if accum < amount && amount < next_accum { - let excessive_amount = next_accum - amount; - remaining_chunks.push(UnlockingChunk{ - amount: excessive_amount, - unlock_era: item.unlock_era, - }); - return amount; - } - - remaining_chunks.push(*item); - accum - }); - - (collected_amount, Self { unlocking_chunks: remaining_chunks }) + ( + collected_amount, + Self { + unlocking_chunks: remaining_chunks, + }, + ) } #[cfg(test)] diff --git a/frame/dapps-staking/src/pallet/mod.rs b/frame/dapps-staking/src/pallet/mod.rs index 25b995ad..abefeab6 100644 --- a/frame/dapps-staking/src/pallet/mod.rs +++ b/frame/dapps-staking/src/pallet/mod.rs @@ -219,7 +219,7 @@ pub mod pallet { T::SmartContract, ), /// Account has rebonded unlocking chunks and staked funds on a smart contract. - RebondAndStake(T::AccountId, T::SmartContract, BalanceOf) + RebondAndStake(T::AccountId, T::SmartContract, BalanceOf), } #[pallet::error] @@ -575,10 +575,10 @@ pub mod pallet { } /// Lock up and stake unbonded chunks of origin account. - /// + /// /// `value` must be more than the `minimum_balance` specified by `MinimumStakingAmount` /// unless account already has bonded value equal or more than 'minimum_balance'. - /// + /// /// The dispatch origin for this call must be _Signed_ by the staker's account. #[pallet::weight(T::WeightInfo::rebond_and_stake())] pub fn rebond_and_stake( @@ -601,10 +601,11 @@ pub mod pallet { !ledger.unbonding_info.is_empty(), Error::::NothingToRebond ); - + // Sort chunks descending order by unlock_era, so that chunks with bigger era_index are collected with priority. ledger.unbonding_info.sort(unlock_era_desc); - let (value_to_stake, mut remaining_chunks) = ledger.unbonding_info.collect_amount(value); + let (value_to_stake, mut remaining_chunks) = + ledger.unbonding_info.collect_amount(value); ensure!( value_to_stake > Zero::zero(), Error::::StakingWithNoValue diff --git a/frame/dapps-staking/src/testing_utils.rs b/frame/dapps-staking/src/testing_utils.rs index d8a2a5f5..872c1392 100644 --- a/frame/dapps-staking/src/testing_utils.rs +++ b/frame/dapps-staking/src/testing_utils.rs @@ -365,11 +365,16 @@ pub(crate) fn assert_rebond_and_stake( // Define expected state after extrinsic init_ledger.unbonding_info.sort(unlock_era_desc); - let (expected_stake_amount, mut expected_remaining_info) = init_ledger.unbonding_info.collect_amount(value); + let (expected_stake_amount, mut expected_remaining_info) = + init_ledger.unbonding_info.collect_amount(value); expected_remaining_info.sort(unlock_era_asc); // Ensure op is successful and event is emitted - assert_ok!(DappsStaking::rebond_and_stake(Origin::signed(staker), contract_id.clone(), value)); + assert_ok!(DappsStaking::rebond_and_stake( + Origin::signed(staker), + contract_id.clone(), + value + )); System::assert_last_event(mock::Event::DappsStaking(Event::RebondAndStake( staker, contract_id.clone(), @@ -379,10 +384,7 @@ pub(crate) fn assert_rebond_and_stake( // Fetch the latest unbonding info so we can compare it to initial unbonding info let final_state = MemorySnapshot::all(current_era, &contract_id, staker); let final_ledger = final_state.ledger.clone(); - assert_eq!( - final_ledger.unbonding_info, - expected_remaining_info, - ); + assert_eq!(final_ledger.unbonding_info, expected_remaining_info,); assert_eq!( final_ledger.locked, init_ledger.locked + expected_stake_amount diff --git a/frame/dapps-staking/src/tests.rs b/frame/dapps-staking/src/tests.rs index f8565890..a404a1b0 100644 --- a/frame/dapps-staking/src/tests.rs +++ b/frame/dapps-staking/src/tests.rs @@ -1080,7 +1080,7 @@ fn rebond_and_stake_is_ok() { // Advance one era and then unbond some more advance_to_era(initial_era + 1); assert_unbond_and_unstake(staker_id, &contract_id, second_unbond_value); - + // unbond and stake assert_rebond_and_stake(staker_id, &contract_id, 300); @@ -1090,8 +1090,7 @@ fn rebond_and_stake_is_ok() { assert!(Ledger::::get(&staker_id) .unbonding_info - .is_empty() - ); + .is_empty()); }) } diff --git a/frame/dapps-staking/src/tests_lib.rs b/frame/dapps-staking/src/tests_lib.rs index 9f991f5e..99d56f41 100644 --- a/frame/dapps-staking/src/tests_lib.rs +++ b/frame/dapps-staking/src/tests_lib.rs @@ -1,5 +1,4 @@ use super::*; -use alloc::vec; use frame_support::assert_ok; use mock::Balance; @@ -56,24 +55,102 @@ fn unbonding_info_test() { assert_eq!(3, first_info.len()); assert_eq!(2, second_info.len()); assert_eq!(unbonding_info.sum(), first_info.sum() + second_info.sum()); + + // Confirm collect_amount works + // println!("{:?}", unbonding_info); + // UnbondingInfo { unlocking_chunks: [UnlockingChunk { amount: 500, unlock_era: 5 }, UnlockingChunk { amount: 400, unlock_era: 8 }, UnlockingChunk { amount: 300, unlock_era: 11 }, UnlockingChunk { amount: 200, unlock_era: 14 }, UnlockingChunk { amount: 100, unlock_era: 17 }] } + let request_amount: Balance = 1300; + let (collected_amount, remaining_chunks) = + unbonding_info.clone().collect_amount(request_amount); + assert_eq!(collected_amount, request_amount); + assert_eq!( + remaining_chunks, + UnbondingInfo:: { + unlocking_chunks: vec![ + UnlockingChunk { + amount: 100, + unlock_era: 14 + }, + UnlockingChunk { + amount: 100, + unlock_era: 17 + }, + ] + } + ); + + // collect amount again from remaining_chunks + let available_amount = remaining_chunks.clone().sum(); + let (collected_amount, remaining_chunks) = remaining_chunks.clone().collect_amount(400); + assert_eq!(collected_amount, available_amount); + assert_eq!( + remaining_chunks, + UnbondingInfo:: { + unlocking_chunks: vec![] + } + ); } -// #[test] -// fn unbonding_info_sort_test() { - // let mut unbonding_info = UnbondingInfo::::default(); - - // // Prepare unlocking chunks. - // let count = 5; - // let base_amount: Balance = 100; - // let base_unlock_era = 4 * count; - // let mut chunks = vec![]; - // for x in 1_u32..=count as u32 { - // chunks.push(UnlockingChunk { - // amount: base_amount * x as Balance, - // unlock_era: base_unlock_era - 3 * x, - // }); - // } -// } +#[test] +fn unbonding_info_sort_test() { + let unlock_era_asc_unbonding_info = UnbondingInfo:: { + unlocking_chunks: vec![ + UnlockingChunk { + amount: 1, + unlock_era: 5, + }, + UnlockingChunk { + amount: 2, + unlock_era: 10, + }, + UnlockingChunk { + amount: 3, + unlock_era: 15, + }, + UnlockingChunk { + amount: 4, + unlock_era: 20, + }, + UnlockingChunk { + amount: 5, + unlock_era: 25, + }, + ], + }; + + let unlock_era_desc_unbonding_info = UnbondingInfo:: { + unlocking_chunks: vec![ + UnlockingChunk { + amount: 5, + unlock_era: 25, + }, + UnlockingChunk { + amount: 4, + unlock_era: 20, + }, + UnlockingChunk { + amount: 3, + unlock_era: 15, + }, + UnlockingChunk { + amount: 2, + unlock_era: 10, + }, + UnlockingChunk { + amount: 1, + unlock_era: 5, + }, + ], + }; + + let mut unbonding_info = unlock_era_desc_unbonding_info.clone(); + unbonding_info.sort(unlock_era_asc); + assert_eq!(unbonding_info, unlock_era_asc_unbonding_info); + + let mut unbonding_info = unlock_era_asc_unbonding_info.clone(); + unbonding_info.sort(unlock_era_desc); + assert_eq!(unbonding_info, unlock_era_desc_unbonding_info); +} #[test] fn staker_info_basic() { From de3ff1e0ded5c578cf0544c33ea0da5cb5a564ea Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Thu, 15 Sep 2022 21:03:09 +0900 Subject: [PATCH 03/15] up --- frame/dapps-staking/src/lib.rs | 53 ------------- frame/dapps-staking/src/pallet/mod.rs | 16 +--- frame/dapps-staking/src/testing_utils.rs | 17 ++--- frame/dapps-staking/src/tests.rs | 14 +--- frame/dapps-staking/src/tests_lib.rs | 95 ------------------------ 5 files changed, 11 insertions(+), 184 deletions(-) diff --git a/frame/dapps-staking/src/lib.rs b/frame/dapps-staking/src/lib.rs index 71eadebc..354efdb7 100644 --- a/frame/dapps-staking/src/lib.rs +++ b/frame/dapps-staking/src/lib.rs @@ -409,20 +409,6 @@ pub struct UnbondingInfo { unlocking_chunks: Vec>, } -pub fn unlock_era_asc( - a: &UnlockingChunk, - b: &UnlockingChunk, -) -> Ordering { - a.unlock_era.cmp(&b.unlock_era) -} - -pub fn unlock_era_desc( - a: &UnlockingChunk, - b: &UnlockingChunk, -) -> Ordering { - b.unlock_era.cmp(&a.unlock_era) -} - impl UnbondingInfo where Balance: AtLeast32BitUnsigned + Default + Copy, @@ -460,13 +446,6 @@ where } } - fn sort(&mut self, compare: F) - where - F: FnMut(&UnlockingChunk, &UnlockingChunk) -> Ordering, - { - self.unlocking_chunks.sort_by(compare); - } - /// Partitions the unlocking chunks into two groups: /// /// First group includes all chunks which have unlock era lesser or equal to the specified era. @@ -492,38 +471,6 @@ where ) } - fn collect_amount(self, amount: Balance) -> (Balance, Self) { - let mut remaining_chunks: Vec> = Default::default(); - let collected_amount = - self.unlocking_chunks - .iter() - .fold(Balance::zero(), |collected, item| { - let next_collected = collected + item.amount; - if next_collected <= amount { - return next_collected; - } - - if collected < amount && amount < next_collected { - let excessive_amount = next_collected - amount; - remaining_chunks.push(UnlockingChunk { - amount: excessive_amount, - unlock_era: item.unlock_era, - }); - return amount; - } - - remaining_chunks.push(*item); - collected - }); - - ( - collected_amount, - Self { - unlocking_chunks: remaining_chunks, - }, - ) - } - #[cfg(test)] /// Return clone of the internal vector. Should only be used for testing. fn vec(&self) -> Vec> { diff --git a/frame/dapps-staking/src/pallet/mod.rs b/frame/dapps-staking/src/pallet/mod.rs index abefeab6..52a5e161 100644 --- a/frame/dapps-staking/src/pallet/mod.rs +++ b/frame/dapps-staking/src/pallet/mod.rs @@ -584,7 +584,6 @@ pub mod pallet { pub fn rebond_and_stake( origin: OriginFor, contract_id: T::SmartContract, - #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { Self::ensure_pallet_enabled()?; let staker = ensure_signed(origin)?; @@ -597,18 +596,10 @@ pub mod pallet { // Get the staking ledger or create an entry if it doesn't exist. let mut ledger = Self::ledger(&staker); - ensure!( - !ledger.unbonding_info.is_empty(), - Error::::NothingToRebond - ); - - // Sort chunks descending order by unlock_era, so that chunks with bigger era_index are collected with priority. - ledger.unbonding_info.sort(unlock_era_desc); - let (value_to_stake, mut remaining_chunks) = - ledger.unbonding_info.collect_amount(value); + let value_to_stake = ledger.unbonding_info.sum(); ensure!( value_to_stake > Zero::zero(), - Error::::StakingWithNoValue + Error::::NothingToRebond ); let current_era = Self::current_era(); @@ -623,9 +614,8 @@ pub mod pallet { current_era, )?; - remaining_chunks.sort(unlock_era_asc); - ledger.unbonding_info = remaining_chunks; ledger.locked = ledger.locked.saturating_add(value_to_stake); + ledger.unbonding_info.unlocking_chunks = Vec::>>::default(); GeneralEraInfo::::mutate(¤t_era, |value| { if let Some(x) = value { diff --git a/frame/dapps-staking/src/testing_utils.rs b/frame/dapps-staking/src/testing_utils.rs index 872c1392..d5cf2beb 100644 --- a/frame/dapps-staking/src/testing_utils.rs +++ b/frame/dapps-staking/src/testing_utils.rs @@ -356,24 +356,18 @@ pub(crate) fn assert_unbond_and_unstake( pub(crate) fn assert_rebond_and_stake( staker: AccountId, contract_id: &MockSmartContract, - value: Balance, ) { // Get latest staking info let current_era = DappsStaking::current_era(); let init_state = MemorySnapshot::all(current_era, &contract_id, staker); - let mut init_ledger = init_state.ledger.clone(); - // Define expected state after extrinsic - init_ledger.unbonding_info.sort(unlock_era_desc); - let (expected_stake_amount, mut expected_remaining_info) = - init_ledger.unbonding_info.collect_amount(value); - expected_remaining_info.sort(unlock_era_asc); + // Define expected stake amount + let expected_stake_amount = init_state.ledger.unbonding_info.sum(); // Ensure op is successful and event is emitted assert_ok!(DappsStaking::rebond_and_stake( Origin::signed(staker), contract_id.clone(), - value )); System::assert_last_event(mock::Event::DappsStaking(Event::RebondAndStake( staker, @@ -383,11 +377,10 @@ pub(crate) fn assert_rebond_and_stake( // Fetch the latest unbonding info so we can compare it to initial unbonding info let final_state = MemorySnapshot::all(current_era, &contract_id, staker); - let final_ledger = final_state.ledger.clone(); - assert_eq!(final_ledger.unbonding_info, expected_remaining_info,); + assert!(final_state.ledger.unbonding_info.is_empty()); assert_eq!( - final_ledger.locked, - init_ledger.locked + expected_stake_amount + final_state.ledger.locked, + init_state.ledger.locked + expected_stake_amount ); // In case staker hasn't been staking this contract until now diff --git a/frame/dapps-staking/src/tests.rs b/frame/dapps-staking/src/tests.rs index a404a1b0..49180ed4 100644 --- a/frame/dapps-staking/src/tests.rs +++ b/frame/dapps-staking/src/tests.rs @@ -1082,15 +1082,7 @@ fn rebond_and_stake_is_ok() { assert_unbond_and_unstake(staker_id, &contract_id, second_unbond_value); // unbond and stake - assert_rebond_and_stake(staker_id, &contract_id, 300); - - // unbond and stake again. - // this time value exceeds the total amount of unbonding chunks, but it succeeds by consuming the total amount. - assert_rebond_and_stake(staker_id, &contract_id, 200); - - assert!(Ledger::::get(&staker_id) - .unbonding_info - .is_empty()); + assert_rebond_and_stake(staker_id, &contract_id); }) } @@ -1108,7 +1100,7 @@ fn rebond_and_stake_unexist_contract_fails() { let non_exist_contract_id = MockSmartContract::Evm(H160::repeat_byte(0x02)); assert_noop!( - DappsStaking::rebond_and_stake(Origin::signed(staker_id), non_exist_contract_id, 200), + DappsStaking::rebond_and_stake(Origin::signed(staker_id), non_exist_contract_id), Error::::NotOperatedContract, ); }) @@ -1124,7 +1116,7 @@ fn rebond_and_stake_no_unbonding_chunks_fails() { assert_register(10, &contract_id); assert_noop!( - DappsStaking::rebond_and_stake(Origin::signed(staker_id), contract_id, 200), + DappsStaking::rebond_and_stake(Origin::signed(staker_id), contract_id), Error::::NothingToRebond, ); }) diff --git a/frame/dapps-staking/src/tests_lib.rs b/frame/dapps-staking/src/tests_lib.rs index 99d56f41..8062265f 100644 --- a/frame/dapps-staking/src/tests_lib.rs +++ b/frame/dapps-staking/src/tests_lib.rs @@ -55,101 +55,6 @@ fn unbonding_info_test() { assert_eq!(3, first_info.len()); assert_eq!(2, second_info.len()); assert_eq!(unbonding_info.sum(), first_info.sum() + second_info.sum()); - - // Confirm collect_amount works - // println!("{:?}", unbonding_info); - // UnbondingInfo { unlocking_chunks: [UnlockingChunk { amount: 500, unlock_era: 5 }, UnlockingChunk { amount: 400, unlock_era: 8 }, UnlockingChunk { amount: 300, unlock_era: 11 }, UnlockingChunk { amount: 200, unlock_era: 14 }, UnlockingChunk { amount: 100, unlock_era: 17 }] } - let request_amount: Balance = 1300; - let (collected_amount, remaining_chunks) = - unbonding_info.clone().collect_amount(request_amount); - assert_eq!(collected_amount, request_amount); - assert_eq!( - remaining_chunks, - UnbondingInfo:: { - unlocking_chunks: vec![ - UnlockingChunk { - amount: 100, - unlock_era: 14 - }, - UnlockingChunk { - amount: 100, - unlock_era: 17 - }, - ] - } - ); - - // collect amount again from remaining_chunks - let available_amount = remaining_chunks.clone().sum(); - let (collected_amount, remaining_chunks) = remaining_chunks.clone().collect_amount(400); - assert_eq!(collected_amount, available_amount); - assert_eq!( - remaining_chunks, - UnbondingInfo:: { - unlocking_chunks: vec![] - } - ); -} - -#[test] -fn unbonding_info_sort_test() { - let unlock_era_asc_unbonding_info = UnbondingInfo:: { - unlocking_chunks: vec![ - UnlockingChunk { - amount: 1, - unlock_era: 5, - }, - UnlockingChunk { - amount: 2, - unlock_era: 10, - }, - UnlockingChunk { - amount: 3, - unlock_era: 15, - }, - UnlockingChunk { - amount: 4, - unlock_era: 20, - }, - UnlockingChunk { - amount: 5, - unlock_era: 25, - }, - ], - }; - - let unlock_era_desc_unbonding_info = UnbondingInfo:: { - unlocking_chunks: vec![ - UnlockingChunk { - amount: 5, - unlock_era: 25, - }, - UnlockingChunk { - amount: 4, - unlock_era: 20, - }, - UnlockingChunk { - amount: 3, - unlock_era: 15, - }, - UnlockingChunk { - amount: 2, - unlock_era: 10, - }, - UnlockingChunk { - amount: 1, - unlock_era: 5, - }, - ], - }; - - let mut unbonding_info = unlock_era_desc_unbonding_info.clone(); - unbonding_info.sort(unlock_era_asc); - assert_eq!(unbonding_info, unlock_era_asc_unbonding_info); - - let mut unbonding_info = unlock_era_asc_unbonding_info.clone(); - unbonding_info.sort(unlock_era_desc); - assert_eq!(unbonding_info, unlock_era_desc_unbonding_info); } #[test] From f36013d311631b8e38e847050d0a7a48501cba6d Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Thu, 15 Sep 2022 21:04:32 +0900 Subject: [PATCH 04/15] cargo fmt --- frame/dapps-staking/src/lib.rs | 1 - frame/dapps-staking/src/pallet/mod.rs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/frame/dapps-staking/src/lib.rs b/frame/dapps-staking/src/lib.rs index 354efdb7..0016dc11 100644 --- a/frame/dapps-staking/src/lib.rs +++ b/frame/dapps-staking/src/lib.rs @@ -48,7 +48,6 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode, HasCompact}; -use core::cmp::Ordering; use frame_support::traits::Currency; use frame_system::{self as system}; use scale_info::TypeInfo; diff --git a/frame/dapps-staking/src/pallet/mod.rs b/frame/dapps-staking/src/pallet/mod.rs index 52a5e161..c8955c5f 100644 --- a/frame/dapps-staking/src/pallet/mod.rs +++ b/frame/dapps-staking/src/pallet/mod.rs @@ -597,10 +597,7 @@ pub mod pallet { // Get the staking ledger or create an entry if it doesn't exist. let mut ledger = Self::ledger(&staker); let value_to_stake = ledger.unbonding_info.sum(); - ensure!( - value_to_stake > Zero::zero(), - Error::::NothingToRebond - ); + ensure!(value_to_stake > Zero::zero(), Error::::NothingToRebond); let current_era = Self::current_era(); let mut staking_info = From 86a2c09029f7d83d1321d0eaa826fdbed87f55a0 Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Fri, 16 Sep 2022 00:06:12 +0900 Subject: [PATCH 05/15] added precompiles and chain extensions --- chain-extensions/dapps-staking/src/lib.rs | 24 ++++++++ .../types/dapps-staking/src/lib.rs | 5 +- frame/dapps-staking/src/weights.rs | 4 +- precompiles/dapps-staking/DappsStaking.sol | 4 ++ precompiles/dapps-staking/src/lib.rs | 22 +++++++ precompiles/dapps-staking/src/tests.rs | 58 +++++++++++++++++++ 6 files changed, 114 insertions(+), 3 deletions(-) diff --git a/chain-extensions/dapps-staking/src/lib.rs b/chain-extensions/dapps-staking/src/lib.rs index c0aaff2c..61829fc7 100644 --- a/chain-extensions/dapps-staking/src/lib.rs +++ b/chain-extensions/dapps-staking/src/lib.rs @@ -37,6 +37,7 @@ enum DappsStakingFunc { ClaimDapp, SetRewardDestination, NominationTransfer, + RebondAndStake, } impl TryFrom for DappsStakingFunc { @@ -58,6 +59,7 @@ impl TryFrom for DappsStakingFunc { 12 => Ok(DappsStakingFunc::ClaimDapp), 13 => Ok(DappsStakingFunc::SetRewardDestination), 14 => Ok(DappsStakingFunc::NominationTransfer), + 15 => Ok(DappsStakingFunc::RebondAndStake), _ => Err(DispatchError::Other( "DappsStakingExtension: Unimplemented func_id", )), @@ -333,6 +335,28 @@ impl ChainExtensionExec for DappsStakingExte Ok(_) => Ok(RetVal::Converging(DSError::Success as u32)), }; } + + DappsStakingFunc::RebondAndStake => { + let contract_bytes: [u8; 32] = env.read_as()?; + let contract = Self::decode_smart_contract(contract_bytes)?; + + let base_weight = + ::WeightInfo::rebond_and_stake(); + env.charge_weight(base_weight)?; + + let caller = env.ext().address().clone(); + let call_result = pallet_dapps_staking::Pallet::::rebond_and_stake( + RawOrigin::Signed(caller).into(), + contract, + ); + return match call_result { + Err(e) => { + let mapped_error = DSError::try_from(e.error)?; + Ok(RetVal::Converging(mapped_error as u32)) + } + Ok(_) => Ok(RetVal::Converging(DSError::Success as u32)), + }; + } } Ok(RetVal::Converging(DSError::Success as u32)) diff --git a/chain-extensions/types/dapps-staking/src/lib.rs b/chain-extensions/types/dapps-staking/src/lib.rs index 4123e896..3f4dcaaf 100644 --- a/chain-extensions/types/dapps-staking/src/lib.rs +++ b/chain-extensions/types/dapps-staking/src/lib.rs @@ -65,6 +65,8 @@ pub enum DSError { NominationTransferToSameContract = 26, /// Unexpected reward destination value RewardDestinationValueOutOfBounds = 27, + /// There are no previously unbonded funds that can be reboneded and staked. + NothingToRebond = 28, /// Unknown error UnknownError = 99, } @@ -105,7 +107,8 @@ impl TryFrom for DSError { Some("NotActiveStaker") => Ok(DSError::NotActiveStaker), Some("NominationTransferToSameContract") => { Ok(DSError::NominationTransferToSameContract) - } + }, + Some("NothingToRebond") => Ok(DSError::NothingToRebond), _ => Ok(DSError::UnknownError), }; } diff --git a/frame/dapps-staking/src/weights.rs b/frame/dapps-staking/src/weights.rs index 0e5098e8..836030b7 100644 --- a/frame/dapps-staking/src/weights.rs +++ b/frame/dapps-staking/src/weights.rs @@ -100,7 +100,7 @@ impl WeightInfo for SubstrateWeight { } // TODO benchmarking fn rebond_and_stake() -> Weight { - todo!() + 1 } // Storage: DappsStaking PalletDisabled (r:1 w:0) // Storage: DappsStaking Ledger (r:1 w:1) @@ -255,7 +255,7 @@ impl WeightInfo for () { } // TODO benchmarking fn rebond_and_stake() -> Weight { - todo!() + 1 } // Storage: DappsStaking PalletDisabled (r:1 w:0) // Storage: DappsStaking Ledger (r:1 w:1) diff --git a/precompiles/dapps-staking/DappsStaking.sol b/precompiles/dapps-staking/DappsStaking.sol index 40d32b0a..af7c3aab 100644 --- a/precompiles/dapps-staking/DappsStaking.sol +++ b/precompiles/dapps-staking/DappsStaking.sol @@ -87,4 +87,8 @@ interface DappsStaking { /// @param amount: The amount to transfer from origin to target /// @param target_smart_contract: The target smart contract address function nomination_transfer(address origin_smart_contract, uint128 amount, address target_smart_contract) external; + + /// @notice Rebond and stake all funds that are in the unbonding process. + /// @param smart_contract: The smart contract address to stake + function rebond_and_stake(address smart_contract) external; } diff --git a/precompiles/dapps-staking/src/lib.rs b/precompiles/dapps-staking/src/lib.rs index 49f26939..7207b422 100644 --- a/precompiles/dapps-staking/src/lib.rs +++ b/precompiles/dapps-staking/src/lib.rs @@ -357,6 +357,26 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } + fn rebond_and_stake(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + + // parse contract's address + let contract_h160 = input.read::
()?.0; + let contract_id = Self::decode_smart_contract(contract_h160)?; + log::trace!(target: "ds-precompile", "rebond_and_stake {:?}", contract_id); + + // Build call with origin. + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapps_staking::Call::::rebond_and_stake { + contract_id, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + /// Helper method to decode type SmartContract enum pub fn decode_smart_contract( contract_h160: H160, @@ -422,6 +442,7 @@ pub enum Action { SetRewardDestination = "set_reward_destination(uint8)", WithdrawFromUnregistered = "withdraw_from_unregistered(address)", NominationTransfer = "nomination_transfer(address,uint128,address)", + RebondAndStake = "rebond_and_stake(address)" } impl Precompile for DappsStakingWrapper @@ -469,6 +490,7 @@ where Action::SetRewardDestination => Self::set_reward_destination(handle), Action::WithdrawFromUnregistered => Self::withdraw_from_unregistered(handle), Action::NominationTransfer => Self::nomination_transfer(handle), + Action::RebondAndStake => Self::rebond_and_stake(handle), } } } diff --git a/precompiles/dapps-staking/src/tests.rs b/precompiles/dapps-staking/src/tests.rs index 24aa67a6..4785e078 100644 --- a/precompiles/dapps-staking/src/tests.rs +++ b/precompiles/dapps-staking/src/tests.rs @@ -375,6 +375,37 @@ fn nomination_transfer() { }); } +#[test] +fn rebond_and_stake_is_ok() { + ExternalityBuilder::default() + .with_balances(vec![ + (TestAccount::Alex.into(), 200 * AST), + (TestAccount::Dino.into(), 200 * AST), + (TestAccount::Bobo.into(), 200 * AST), + ]) + .build() + .execute_with(|| { + initialize_first_block(); + + // register new contract by Alex + let developer = TestAccount::Alex.into(); + register_and_verify(developer, TEST_CONTRACT); + + let amount_staked = 100 * AST; + bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked); + + // unstak all + let era = 2; + advance_to_era(era); + unbond_unstake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked); + + // rebond unbonded funds + let era = 3; + advance_to_era(era); + rebond_and_stake_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked); + }) +} + // **************************************************************************************************** // Helper functions // **************************************************************************************************** @@ -593,6 +624,33 @@ fn nomination_transfer_verify( ); } +// Since rebond_amount is not accessible (private field), need to provide expected amout to be rebonded. +fn rebond_and_stake_verify(staker: TestAccount, contract: H160, expected_rebond_amount: Balance) { + let smart_contract = + decode_smart_contract_from_array(contract.clone().to_fixed_bytes()).unwrap(); + + let staker_acc_id = AccountId32::from(staker.clone()); + let init_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); + + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::RebondAndStake) + .write(Address(contract.clone())) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let final_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); + + assert_eq!( + final_staker_info.latest_staked_value(), + init_staker_info.latest_staked_value() + expected_rebond_amount + ); +} + /// helper function to bond, stake and verify if result is OK fn claim_dapp_and_verify(contract: H160, era: EraIndex) { precompiles() From c391f65a15bfda7ded72c131702f7a303bdbc626 Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Fri, 16 Sep 2022 00:06:30 +0900 Subject: [PATCH 06/15] cargo fmt --- chain-extensions/types/dapps-staking/src/lib.rs | 2 +- precompiles/dapps-staking/src/lib.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/chain-extensions/types/dapps-staking/src/lib.rs b/chain-extensions/types/dapps-staking/src/lib.rs index 3f4dcaaf..c792e309 100644 --- a/chain-extensions/types/dapps-staking/src/lib.rs +++ b/chain-extensions/types/dapps-staking/src/lib.rs @@ -107,7 +107,7 @@ impl TryFrom for DSError { Some("NotActiveStaker") => Ok(DSError::NotActiveStaker), Some("NominationTransferToSameContract") => { Ok(DSError::NominationTransferToSameContract) - }, + } Some("NothingToRebond") => Ok(DSError::NothingToRebond), _ => Ok(DSError::UnknownError), }; diff --git a/precompiles/dapps-staking/src/lib.rs b/precompiles/dapps-staking/src/lib.rs index 7207b422..84ffbebc 100644 --- a/precompiles/dapps-staking/src/lib.rs +++ b/precompiles/dapps-staking/src/lib.rs @@ -368,9 +368,7 @@ where // Build call with origin. let origin = R::AddressMapping::into_account_id(handle.context().caller); - let call = pallet_dapps_staking::Call::::rebond_and_stake { - contract_id, - }; + let call = pallet_dapps_staking::Call::::rebond_and_stake { contract_id }; RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; @@ -442,7 +440,7 @@ pub enum Action { SetRewardDestination = "set_reward_destination(uint8)", WithdrawFromUnregistered = "withdraw_from_unregistered(address)", NominationTransfer = "nomination_transfer(address,uint128,address)", - RebondAndStake = "rebond_and_stake(address)" + RebondAndStake = "rebond_and_stake(address)", } impl Precompile for DappsStakingWrapper From f27a1adee220714966256b4f11c388df4439f119 Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Fri, 16 Sep 2022 14:38:56 +0900 Subject: [PATCH 07/15] benchmarking --- frame/dapps-staking/src/benchmarking.rs | 18 ++++++++++++++++++ frame/dapps-staking/src/tests.rs | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/frame/dapps-staking/src/benchmarking.rs b/frame/dapps-staking/src/benchmarking.rs index 9340ca29..2c19a94e 100644 --- a/frame/dapps-staking/src/benchmarking.rs +++ b/frame/dapps-staking/src/benchmarking.rs @@ -217,6 +217,24 @@ benchmarks! { assert_last_event::(Event::::NominationTransfer(staker, origin_contract_id, T::MinimumStakingAmount::get(), target_contract_id).into()); } + rebond_and_stake { + initialize::(); + + let (_, contract_id) = register_contract::(1)?; + prepare_bond_and_stake::(T::MaxNumberOfStakersPerContract::get() - 1, &contract_id, SEED)?; + + let staker = whitelisted_caller(); + let _ = T::Currency::make_free_balance_be(&staker, BalanceOf::::max_value()); + let stake_amount = BalanceOf::::max_value() / 2u32.into(); + let unstake_amount = stake_amount / 2u32.into(); + + DappsStaking::::bond_and_stake(RawOrigin::Signed(staker.clone()).into(), contract_id.clone(), stake_amount)?; + DappsStaking::::unbond_and_unstake(RawOrigin::Signed(staker.clone()).into(), contract_id, unstake_amount)?; + }: _(RawOrigin::Signed(staker.clone())) + verify { + assert_last_event::(Event::::RebondAndStake(staker, contract_id, unstake_amount).into()); + } + claim_staker_with_restake { initialize::(); let (_, contract_id) = register_contract::(1)?; diff --git a/frame/dapps-staking/src/tests.rs b/frame/dapps-staking/src/tests.rs index 49180ed4..401faddd 100644 --- a/frame/dapps-staking/src/tests.rs +++ b/frame/dapps-staking/src/tests.rs @@ -1081,7 +1081,7 @@ fn rebond_and_stake_is_ok() { advance_to_era(initial_era + 1); assert_unbond_and_unstake(staker_id, &contract_id, second_unbond_value); - // unbond and stake + // rebond and stake assert_rebond_and_stake(staker_id, &contract_id); }) } From 292cfeee58d7a00474fdd139c33460fa2ce2a2ec Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Fri, 16 Sep 2022 15:14:44 +0900 Subject: [PATCH 08/15] fix bench --- frame/dapps-staking/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/dapps-staking/src/benchmarking.rs b/frame/dapps-staking/src/benchmarking.rs index 2c19a94e..d2598d97 100644 --- a/frame/dapps-staking/src/benchmarking.rs +++ b/frame/dapps-staking/src/benchmarking.rs @@ -230,7 +230,7 @@ benchmarks! { DappsStaking::::bond_and_stake(RawOrigin::Signed(staker.clone()).into(), contract_id.clone(), stake_amount)?; DappsStaking::::unbond_and_unstake(RawOrigin::Signed(staker.clone()).into(), contract_id, unstake_amount)?; - }: _(RawOrigin::Signed(staker.clone())) + }: _(RawOrigin::Signed(staker.clone()), contract_id.clone()) verify { assert_last_event::(Event::::RebondAndStake(staker, contract_id, unstake_amount).into()); } From 719024da774bf31940b18ff7f96b9ed543d94170 Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Fri, 16 Sep 2022 15:16:15 +0900 Subject: [PATCH 09/15] fix bench --- frame/dapps-staking/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/dapps-staking/src/benchmarking.rs b/frame/dapps-staking/src/benchmarking.rs index d2598d97..3e62f7c2 100644 --- a/frame/dapps-staking/src/benchmarking.rs +++ b/frame/dapps-staking/src/benchmarking.rs @@ -229,7 +229,7 @@ benchmarks! { let unstake_amount = stake_amount / 2u32.into(); DappsStaking::::bond_and_stake(RawOrigin::Signed(staker.clone()).into(), contract_id.clone(), stake_amount)?; - DappsStaking::::unbond_and_unstake(RawOrigin::Signed(staker.clone()).into(), contract_id, unstake_amount)?; + DappsStaking::::unbond_and_unstake(RawOrigin::Signed(staker.clone()).into(), contract_id.clone(), unstake_amount)?; }: _(RawOrigin::Signed(staker.clone()), contract_id.clone()) verify { assert_last_event::(Event::::RebondAndStake(staker, contract_id, unstake_amount).into()); From 93286fcf2fbe257f1e4f560fe1e5f0629c170389 Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Fri, 16 Sep 2022 15:40:29 +0900 Subject: [PATCH 10/15] updated weights info --- frame/dapps-staking/src/weights.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/frame/dapps-staking/src/weights.rs b/frame/dapps-staking/src/weights.rs index 836030b7..826b42d6 100644 --- a/frame/dapps-staking/src/weights.rs +++ b/frame/dapps-staking/src/weights.rs @@ -98,9 +98,18 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } - // TODO benchmarking + // Storage: DappsStaking PalletDisabled (r:1 w:0) + // Storage: DappsStaking RegisteredDapps (r:1 w:0) + // Storage: DappsStaking Ledger (r:1 w:1) + // Storage: DappsStaking CurrentEra (r:1 w:0) + // Storage: DappsStaking ContractEraStake (r:1 w:1) + // Storage: DappsStaking GeneralStakerInfo (r:1 w:1) + // Storage: DappsStaking GeneralEraInfo (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) fn rebond_and_stake() -> Weight { - 1 + (133_638_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: DappsStaking PalletDisabled (r:1 w:0) // Storage: DappsStaking Ledger (r:1 w:1) @@ -253,9 +262,18 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } - // TODO benchmarking + // Storage: DappsStaking PalletDisabled (r:1 w:0) + // Storage: DappsStaking RegisteredDapps (r:1 w:0) + // Storage: DappsStaking Ledger (r:1 w:1) + // Storage: DappsStaking CurrentEra (r:1 w:0) + // Storage: DappsStaking ContractEraStake (r:1 w:1) + // Storage: DappsStaking GeneralStakerInfo (r:1 w:1) + // Storage: DappsStaking GeneralEraInfo (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) fn rebond_and_stake() -> Weight { - 1 + (133_638_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: DappsStaking PalletDisabled (r:1 w:0) // Storage: DappsStaking Ledger (r:1 w:1) From 7059ffa2cd364307670f6e9d0fb527c4ea97d2ff Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Fri, 16 Sep 2022 16:09:03 +0900 Subject: [PATCH 11/15] fix locked amount --- frame/dapps-staking/src/pallet/mod.rs | 4 +--- frame/dapps-staking/src/testing_utils.rs | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frame/dapps-staking/src/pallet/mod.rs b/frame/dapps-staking/src/pallet/mod.rs index c8955c5f..2e9c7ede 100644 --- a/frame/dapps-staking/src/pallet/mod.rs +++ b/frame/dapps-staking/src/pallet/mod.rs @@ -576,8 +576,7 @@ pub mod pallet { /// Lock up and stake unbonded chunks of origin account. /// - /// `value` must be more than the `minimum_balance` specified by `MinimumStakingAmount` - /// unless account already has bonded value equal or more than 'minimum_balance'. + /// All unbonding chunks will be used and staked to the specified contract. /// /// The dispatch origin for this call must be _Signed_ by the staker's account. #[pallet::weight(T::WeightInfo::rebond_and_stake())] @@ -611,7 +610,6 @@ pub mod pallet { current_era, )?; - ledger.locked = ledger.locked.saturating_add(value_to_stake); ledger.unbonding_info.unlocking_chunks = Vec::>>::default(); GeneralEraInfo::::mutate(¤t_era, |value| { diff --git a/frame/dapps-staking/src/testing_utils.rs b/frame/dapps-staking/src/testing_utils.rs index d5cf2beb..883ae5c3 100644 --- a/frame/dapps-staking/src/testing_utils.rs +++ b/frame/dapps-staking/src/testing_utils.rs @@ -378,10 +378,10 @@ pub(crate) fn assert_rebond_and_stake( // Fetch the latest unbonding info so we can compare it to initial unbonding info let final_state = MemorySnapshot::all(current_era, &contract_id, staker); assert!(final_state.ledger.unbonding_info.is_empty()); - assert_eq!( - final_state.ledger.locked, - init_state.ledger.locked + expected_stake_amount - ); + + // locked amount before and after the operation should be the same. + // unlocking chunks are still locked unless it is withdrawn. + assert_eq!(final_state.ledger.locked, init_state.ledger.locked); // In case staker hasn't been staking this contract until now if init_state.staker_info.latest_staked_value() == 0 { From 852fb39ac8f512e7c7765a78e33be761e81dbc99 Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Mon, 19 Sep 2022 17:09:51 +0900 Subject: [PATCH 12/15] update test --- frame/dapps-staking/src/tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frame/dapps-staking/src/tests.rs b/frame/dapps-staking/src/tests.rs index 401faddd..30475743 100644 --- a/frame/dapps-staking/src/tests.rs +++ b/frame/dapps-staking/src/tests.rs @@ -1065,7 +1065,9 @@ fn rebond_and_stake_is_ok() { initialize_first_block(); let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + let rebond_contract_id = MockSmartContract::Evm(H160::repeat_byte(0x02)); assert_register(10, &contract_id); + assert_register(11, &rebond_contract_id); let staker_id = 1; assert_bond_and_stake(staker_id, &contract_id, 1000); @@ -1082,7 +1084,7 @@ fn rebond_and_stake_is_ok() { assert_unbond_and_unstake(staker_id, &contract_id, second_unbond_value); // rebond and stake - assert_rebond_and_stake(staker_id, &contract_id); + assert_rebond_and_stake(staker_id, &rebond_contract_id); }) } From 7a8f3b59e628ef72d5f7ad6d02684f8820b2616c Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Wed, 21 Sep 2022 17:33:46 +0900 Subject: [PATCH 13/15] up --- chain-extensions/dapps-staking/src/lib.rs | 8 ++--- .../types/dapps-staking/src/lib.rs | 34 ++++++++++--------- frame/dapps-staking/src/benchmarking.rs | 3 ++ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/chain-extensions/dapps-staking/src/lib.rs b/chain-extensions/dapps-staking/src/lib.rs index 7f198abd..c5b8cf39 100644 --- a/chain-extensions/dapps-staking/src/lib.rs +++ b/chain-extensions/dapps-staking/src/lib.rs @@ -8,7 +8,7 @@ use chain_extension_trait::ChainExtensionExec; use codec::{Decode, Encode}; use dapps_staking_chain_extension_types::{ Contract, DSError, DappsStakingAccountInput, DappsStakingEraInput, DappsStakingNominationInput, - DappsStakingValueInput, + DappsStakingValueInput, ContractBytes, }; use frame_support::traits::{Currency, Get}; use frame_system::RawOrigin; @@ -148,7 +148,7 @@ impl ChainExtensionExec for DappsStakingExte } DappsStakingFunc::ReadContractStake => { - let contract_bytes: [u8; 32] = env.read_as()?; + let contract_bytes: ContractBytes = env.read_as()?; let contract = Self::decode_smart_contract(contract_bytes)?; let base_weight = ::DbWeight::get().read; @@ -229,7 +229,7 @@ impl ChainExtensionExec for DappsStakingExte } DappsStakingFunc::ClaimStaker => { - let contract_bytes: [u8; 32] = env.read_as()?; + let contract_bytes: ContractBytes = env.read_as()?; let contract = Self::decode_smart_contract(contract_bytes)?; let base_weight = T::WeightInfo::claim_staker_with_restake() @@ -340,7 +340,7 @@ impl ChainExtensionExec for DappsStakingExte } DappsStakingFunc::RebondAndStake => { - let contract_bytes: [u8; 32] = env.read_as()?; + let contract_bytes: ContractBytes = env.read_as()?; let contract = Self::decode_smart_contract(contract_bytes)?; let base_weight = diff --git a/chain-extensions/types/dapps-staking/src/lib.rs b/chain-extensions/types/dapps-staking/src/lib.rs index c792e309..b6a7fca7 100644 --- a/chain-extensions/types/dapps-staking/src/lib.rs +++ b/chain-extensions/types/dapps-staking/src/lib.rs @@ -13,25 +13,25 @@ pub enum DSError { Disabled = 1, /// No change in maintenance mode NoMaintenanceModeChange = 2, - /// Upgrade is too heavy, reduce the weight parameter. + /// Upgrade is too heavy, reduce the weight parameter UpgradeTooHeavy = 3, - /// Can not stake with zero value. + /// Can not stake with zero value StakingWithNoValue = 4, /// Can not stake with value less than minimum staking value InsufficientValue = 5, - /// Number of stakers per contract exceeded. + /// Number of stakers per contract exceeded MaxNumberOfStakersExceeded = 6, /// Targets must be operated contracts NotOperatedContract = 7, - /// Contract isn't staked. + /// Contract isn't staked NotStakedContract = 8, /// Contract isn't unregistered. NotUnregisteredContract = 9, - /// Unclaimed rewards should be claimed before withdrawing stake. + /// Unclaimed rewards should be claimed before withdrawing stake UnclaimedRewardsRemaining = 10, /// Unstaking a contract with zero value UnstakingWithNoValue = 11, - /// There are no previously unbonded funds that can be unstaked and withdrawn. + /// There are no previously unbonded funds that can be unstaked and withdrawn NothingToWithdraw = 12, /// The contract is already registered by other account AlreadyRegisteredContract = 13, @@ -39,21 +39,21 @@ pub enum DSError { ContractIsNotValid = 14, /// This account was already used to register contract AlreadyUsedDeveloperAccount = 15, - /// Smart contract not owned by the account id. + /// Smart contract not owned by the account id NotOwnedContract = 16, /// Report issue on github if this is ever emitted UnknownEraReward = 17, /// Report issue on github if this is ever emitted UnexpectedStakeInfoEra = 18, /// Contract has too many unlocking chunks. Withdraw the existing chunks if possible - /// or wait for current chunks to complete unlocking process to withdraw them. + /// or wait for current chunks to complete unlocking process to withdraw them TooManyUnlockingChunks = 19, /// Contract already claimed in this era and reward is distributed AlreadyClaimedInThisEra = 20, /// Era parameter is out of bounds EraOutOfBounds = 21, - /// Too many active `EraStake` values for (staker, contract) pairing. - /// Claim existing rewards to fix this problem. + /// Too many active `EraStake` values for (staker, contract) pairing + /// Claim existing rewards to fix this problem TooManyEraStakeValues = 22, /// To register a contract, pre-approval is needed for this address RequiredContractPreApproval = 23, @@ -65,7 +65,7 @@ pub enum DSError { NominationTransferToSameContract = 26, /// Unexpected reward destination value RewardDestinationValueOutOfBounds = 27, - /// There are no previously unbonded funds that can be reboneded and staked. + /// There are no previously unbonded funds that can be reboneded and staked NothingToRebond = 28, /// Unknown error UnknownError = 99, @@ -123,27 +123,29 @@ pub enum Contract { Wasm(Account), } +pub type ContractBytes = [u8; 32]; + #[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen)] pub struct DappsStakingValueInput { - pub contract: [u8; 32], + pub contract: ContractBytes, pub value: Balance, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen)] pub struct DappsStakingAccountInput { - pub contract: [u8; 32], + pub contract: ContractBytes, pub staker: [u8; 32], } #[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen)] pub struct DappsStakingEraInput { - pub contract: [u8; 32], + pub contract: ContractBytes, pub era: u32, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen)] pub struct DappsStakingNominationInput { - pub origin_contract: [u8; 32], - pub target_contract: [u8; 32], + pub origin_contract: ContractBytes, + pub target_contract: ContractBytes, pub value: Balance, } diff --git a/frame/dapps-staking/src/benchmarking.rs b/frame/dapps-staking/src/benchmarking.rs index 3e62f7c2..292e2b1b 100644 --- a/frame/dapps-staking/src/benchmarking.rs +++ b/frame/dapps-staking/src/benchmarking.rs @@ -228,6 +228,9 @@ benchmarks! { let stake_amount = BalanceOf::::max_value() / 2u32.into(); let unstake_amount = stake_amount / 2u32.into(); + // rebond_and_stake works only when user has unlocking_chunks. + // before executing it, need to prepare the valid state by bond and unbond. + // unbonded funds remain as unlocking_chunks unless it is withdrawn. DappsStaking::::bond_and_stake(RawOrigin::Signed(staker.clone()).into(), contract_id.clone(), stake_amount)?; DappsStaking::::unbond_and_unstake(RawOrigin::Signed(staker.clone()).into(), contract_id.clone(), unstake_amount)?; }: _(RawOrigin::Signed(staker.clone()), contract_id.clone()) From 5c36af9fe0447cbbd283f177512291bc5d96c8f6 Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Wed, 21 Sep 2022 17:35:05 +0900 Subject: [PATCH 14/15] cargo fmt --- chain-extensions/dapps-staking/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chain-extensions/dapps-staking/src/lib.rs b/chain-extensions/dapps-staking/src/lib.rs index c5b8cf39..a0e63346 100644 --- a/chain-extensions/dapps-staking/src/lib.rs +++ b/chain-extensions/dapps-staking/src/lib.rs @@ -7,8 +7,8 @@ use sp_runtime::{ use chain_extension_trait::ChainExtensionExec; use codec::{Decode, Encode}; use dapps_staking_chain_extension_types::{ - Contract, DSError, DappsStakingAccountInput, DappsStakingEraInput, DappsStakingNominationInput, - DappsStakingValueInput, ContractBytes, + Contract, ContractBytes, DSError, DappsStakingAccountInput, DappsStakingEraInput, + DappsStakingNominationInput, DappsStakingValueInput, }; use frame_support::traits::{Currency, Get}; use frame_system::RawOrigin; From a3d98b1d7df82cb6bcf4946b8a962945efd31179 Mon Sep 17 00:00:00 2001 From: Shunsuke Watanabe Date: Fri, 23 Sep 2022 13:24:43 +0900 Subject: [PATCH 15/15] changed branch base to polkadot-v0.9.29 --- frame/dapps-staking/src/weights.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/dapps-staking/src/weights.rs b/frame/dapps-staking/src/weights.rs index ce9e5d7e..fa18eb2a 100644 --- a/frame/dapps-staking/src/weights.rs +++ b/frame/dapps-staking/src/weights.rs @@ -107,9 +107,9 @@ impl WeightInfo for SubstrateWeight { // Storage: DappsStaking GeneralEraInfo (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn rebond_and_stake() -> Weight { - (133_638_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + Weight::from_ref_time(133_638_000 as u64) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: DappsStaking PalletDisabled (r:1 w:0) // Storage: DappsStaking Ledger (r:1 w:1) @@ -271,9 +271,9 @@ impl WeightInfo for () { // Storage: DappsStaking GeneralEraInfo (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn rebond_and_stake() -> Weight { - (133_638_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + Weight::from_ref_time(133_638_000 as u64) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: DappsStaking PalletDisabled (r:1 w:0) // Storage: DappsStaking Ledger (r:1 w:1)