From 6779ccb3cbf1ee8d0e8972712d4967105e821e5c Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Wed, 3 Jul 2024 12:15:53 +0200 Subject: [PATCH 01/24] first dispatchable for teerdays --- Cargo.lock | 32 +++++++ Cargo.toml | 18 ++-- primitives/teerdays/Cargo.toml | 25 ++++++ primitives/teerdays/src/lib.rs | 11 +++ teerdays/Cargo.toml | 55 ++++++++++++ teerdays/src/lib.rs | 121 ++++++++++++++++++++++++++ teerdays/src/mock.rs | 149 +++++++++++++++++++++++++++++++++ teerdays/src/tests.rs | 24 ++++++ teerdays/src/weights.rs | 46 ++++++++++ 9 files changed, 473 insertions(+), 8 deletions(-) create mode 100644 primitives/teerdays/Cargo.toml create mode 100644 primitives/teerdays/src/lib.rs create mode 100644 teerdays/Cargo.toml create mode 100644 teerdays/src/lib.rs create mode 100644 teerdays/src/mock.rs create mode 100644 teerdays/src/tests.rs create mode 100644 teerdays/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 6741b579..94b64111 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2671,6 +2671,27 @@ dependencies = [ "test-utils", ] +[[package]] +name = "pallet-teerdays" +version = "0.1.0" +dependencies = [ + "env_logger", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", + "teerdays-primitives", +] + [[package]] name = "pallet-teerex" version = "0.10.0" @@ -4729,6 +4750,17 @@ dependencies = [ "substrate-fixed", ] +[[package]] +name = "teerdays-primitives" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-runtime", +] + [[package]] name = "teerex-primitives" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a104be06..2d81cb46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,20 +4,22 @@ members = [ "asset-registry", "claims", "enclave-bridge", - "teerex", "parentchain", - "sidechain", - "teerex/sgx-verify", - "teeracle", - "test-utils", - "xcm-transactor", "primitives/claims", + "primitives/common", "primitives/enclave-bridge", - "primitives/teerex", "primitives/teeracle", - "primitives/common", + "primitives/teerdays", + "primitives/teerex", "primitives/xcm", "primitives/xcm-transactor", + "sidechain", + "teeracle", + "teerdays", + "teerex", + "teerex/sgx-verify", + "test-utils", + "xcm-transactor", ] [workspace.dependencies] diff --git a/primitives/teerdays/Cargo.toml b/primitives/teerdays/Cargo.toml new file mode 100644 index 00000000..49395ba0 --- /dev/null +++ b/primitives/teerdays/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "teerdays-primitives" +version = "0.1.0" +authors = ["Integritee AG "] +homepage = "https://integritee.network/" +repository = "https://github.com/integritee-network/pallets/" +license = "Apache-2.0" +edition = "2021" + +[dependencies] +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +serde = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "scale-info/std", + "serde/std", + "sp-core/std", + "sp-runtime/std", +] \ No newline at end of file diff --git a/primitives/teerdays/src/lib.rs b/primitives/teerdays/src/lib.rs new file mode 100644 index 00000000..d3021919 --- /dev/null +++ b/primitives/teerdays/src/lib.rs @@ -0,0 +1,11 @@ +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::Permill; + +pub const TEERDAY: Permill = Permill::from_percent(100); +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Default, sp_core::RuntimeDebug, TypeInfo)] +pub struct TeerDayBond { + pub bond: Balance, + pub last_updated: Moment, + pub accumulated_teerdays: Balance, +} diff --git a/teerdays/Cargo.toml b/teerdays/Cargo.toml new file mode 100644 index 00000000..eeda3b2d --- /dev/null +++ b/teerdays/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "pallet-teerdays" +version = "0.1.0" +authors = ["Integritee AG "] +homepage = "https://integritee.network/" +repository = "https://github.com/integritee-network/pallets/" +license = "(GPL-3.0-only)" +edition = "2021" + +[dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +serde = { workspace = true, optional = true } + +teerdays-primitives = { path = "../primitives/teerdays", default-features = false } + +# substrate dependencies +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +env_logger = { workspace = true } +sp-keyring = { workspace = true } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "teerdays-primitives/std", + "log/std", + "pallet-balances/std", + "parity-scale-codec/std", + "scale-info/std", + "serde/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-keyring/std", +] + +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs new file mode 100644 index 00000000..019842b7 --- /dev/null +++ b/teerdays/src/lib.rs @@ -0,0 +1,121 @@ +/* + Copyright 2021 Integritee AG + + Licenced under GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. You may obtain a copy of the + License at + + . + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//! # Teerdays Pallet +//! A pallet which allows bonding native TEER tokens and accumulate TEERdays the longer the tokens are bonded +//! TEERdays will serve as a basis for governance and other features in the future + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::{Currency, LockIdentifier}; +pub use pallet::*; +use teerdays_primitives::TeerDayBond; + +pub(crate) const TEERDAYS_ID: LockIdentifier = *b"teerdays"; +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +pub type TeerDayBondOf = TeerDayBond, ::Moment>; +#[frame_support::pallet] +pub mod pallet { + use crate::{weights::WeightInfo, BalanceOf, TeerDayBondOf, TEERDAYS_ID}; + use frame_support::{ + pallet_prelude::*, + traits::{Currency, InspectLockableCurrency, LockableCurrency, WithdrawReasons}, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::Zero; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + /// Configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config + pallet_timestamp::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type WeightInfo: WeightInfo; + + /// The bonding balance. + type Currency: LockableCurrency> + + InspectLockableCurrency; + + #[pallet::constant] + type MomentsPerDay: Get; + } + + #[pallet::event] + #[pallet::generate_deposit(pub (super) fn deposit_event)] + pub enum Event { + Bonded { account: T::AccountId, amount: BalanceOf }, + } + + #[pallet::error] + pub enum Error { + /// Each account can only bond once + AlreadyBonded, + /// Insufficient bond + InsufficientBond, + /// Some corruption in internal state. + BadState, + } + + /// Lazy + #[pallet::storage] + #[pallet::getter(fn teerdays)] + pub type TeerDays = + StorageMap<_, Blake2_128Concat, T::AccountId, TeerDayBondOf, OptionQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::bond())] + pub fn bond( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + ) -> DispatchResult { + let signer = ensure_signed(origin)?; + ensure!(!TeerDays::::contains_key(&signer), Error::::AlreadyBonded); + ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientBond); + + frame_system::Pallet::::inc_consumers(&signer).map_err(|_| Error::::BadState)?; + + let free_balance = T::Currency::free_balance(&signer); + let value = value.min(free_balance); + Self::deposit_event(Event::::Bonded { account: signer.clone(), amount: value }); + T::Currency::set_lock(TEERDAYS_ID, &signer, value, WithdrawReasons::all()); + let teerday_bond = TeerDayBondOf:: { + bond: value, + last_updated: pallet_timestamp::Pallet::::get(), + accumulated_teerdays: BalanceOf::::zero(), + }; + TeerDays::::insert(&signer, teerday_bond); + Ok(()) + } + } +} + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; diff --git a/teerdays/src/mock.rs b/teerdays/src/mock.rs new file mode 100644 index 00000000..1e8947be --- /dev/null +++ b/teerdays/src/mock.rs @@ -0,0 +1,149 @@ +/* + Copyright 2021 Integritee AG + + Licenced under GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. You may obtain a copy of the + License at + + . + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Creating mock runtime here +use crate as pallet_teerdays; +use frame_support::{derive_impl, parameter_types}; +use frame_system as system; +use pallet_teerdays::Config; +use sp_core::H256; +use sp_keyring::AccountKeyring; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, +}; + +pub type Signature = sp_runtime::MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; +pub type Address = sp_runtime::MultiAddress; + +pub type BlockNumber = u32; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; + +pub type SignedExtra = ( + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, +); + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + TeerDays: crate::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; +} +#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Block = generic::Block; + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +pub type Balance = u64; + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = (); + type ReserveIdentifier = (); + type RuntimeFreezeReason = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); +} + +parameter_types! { + pub const MinimumPeriod: u64 = 6000 / 2; +} + +pub type Moment = u64; + +impl pallet_timestamp::Config for Test { + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const MomentsPerDay: u64 = 86_400_000; // [ms/d] +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type MomentsPerDay = MomentsPerDay; + type WeightInfo = (); + type Currency = Balances; +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. RA from enclave compiled in debug mode is allowed +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(AccountKeyring::Alice.to_account_id(), 1 << 60)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext: sp_io::TestExternalities = t.into(); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs new file mode 100644 index 00000000..d0db2bac --- /dev/null +++ b/teerdays/src/tests.rs @@ -0,0 +1,24 @@ +use crate::{mock::*, BalanceOf, Event as TeerDaysEvent}; +use frame_support::assert_ok; +use sp_keyring::AccountKeyring; +#[test] +fn bonding_works() { + new_test_ext().execute_with(|| { + let now: Moment = 42; + pallet_timestamp::Pallet::::set(RuntimeOrigin::none(), now) + .expect("set timestamp should work"); + let alice = AccountKeyring::Alice.to_account_id(); + let amount: BalanceOf = 10_000_000_000_000; + assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); + + let expected_event = + RuntimeEvent::TeerDays(TeerDaysEvent::Bonded { account: alice.clone(), amount }); + assert!(System::events().iter().any(|a| a.event == expected_event)); + + let teerdays = + TeerDays::teerdays(&alice).expect("TeerDays entry for bonded account should exist"); + assert_eq!(teerdays.bond, amount); + assert_eq!(teerdays.accumulated_teerdays, 0); + assert_eq!(teerdays.last_updated, now); + }) +} diff --git a/teerdays/src/weights.rs b/teerdays/src/weights.rs new file mode 100644 index 00000000..2fa30d82 --- /dev/null +++ b/teerdays/src/weights.rs @@ -0,0 +1,46 @@ +/* + Copyright 2021 Integritee AG + + Licenced under GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. You may obtain a copy of the + License at + + . + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_teerdays. +pub trait WeightInfo { + fn bond() -> Weight; +} + +/// Weights for pallet_sidechain using the Integritee parachain node and recommended hardware. +pub struct IntegriteeWeight(PhantomData); +impl WeightInfo for IntegriteeWeight { + fn bond() -> Weight { + Weight::from_parts(46_200_000, 0u64) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} + +// For tests +impl WeightInfo for () { + fn bond() -> Weight { + Weight::from_parts(46_200_000, 0u64) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} From 0658f41831130f7b7f62fa24e5fc385a07a29738 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Wed, 3 Jul 2024 21:49:43 +0200 Subject: [PATCH 02/24] basic bonding and unbonding without unlockingperiod. type trouble --- primitives/teerdays/src/lib.rs | 27 ++++++-- teerdays/src/lib.rs | 112 ++++++++++++++++++++++++++++++--- teerdays/src/mock.rs | 7 ++- teerdays/src/tests.rs | 101 +++++++++++++++++++++++++++-- teerdays/src/weights.rs | 11 ++++ 5 files changed, 235 insertions(+), 23 deletions(-) diff --git a/primitives/teerdays/src/lib.rs b/primitives/teerdays/src/lib.rs index d3021919..1ccea675 100644 --- a/primitives/teerdays/src/lib.rs +++ b/primitives/teerdays/src/lib.rs @@ -1,11 +1,30 @@ -use parity_scale_codec::{Decode, Encode}; +use parity_scale_codec::{Compact, Decode, Encode}; use scale_info::TypeInfo; -use sp_runtime::Permill; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, UniqueSaturatedFrom}, + SaturatedConversion, Saturating, +}; +use std::ops::Mul; -pub const TEERDAY: Permill = Permill::from_percent(100); #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Default, sp_core::RuntimeDebug, TypeInfo)] pub struct TeerDayBond { pub bond: Balance, pub last_updated: Moment, - pub accumulated_teerdays: Balance, + // the unit here is actually balance * moments. + pub accumulated_tokentime: Balance, +} + +impl TeerDayBond +where + Moment: Clone + Copy + Encode + Decode + Saturating, + Balance: AtLeast32BitUnsigned + Clone + Copy + Encode + Decode + Default, +{ + pub fn update(self, now: Moment) -> Self { + let elapsed = now.saturating_sub(self.last_updated); + let elapsed_enc = Encode::encode(&elapsed); + let elapsed = Balance::decode(&mut &elapsed_enc[..]).unwrap(); //fixme! + let new_tokentime = + self.accumulated_tokentime.saturating_add(self.bond.saturating_mul(elapsed)); + Self { bond: self.bond, last_updated: now, accumulated_tokentime: new_tokentime } + } } diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 019842b7..f9fe9279 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -21,7 +21,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{Currency, LockIdentifier}; +use frame_support::{ + pallet_prelude::DispatchResult, + traits::{Currency, LockIdentifier}, +}; pub use pallet::*; use teerdays_primitives::TeerDayBond; @@ -38,7 +41,10 @@ pub mod pallet { traits::{Currency, InspectLockableCurrency, LockableCurrency, WithdrawReasons}, }; use frame_system::pallet_prelude::*; - use sp_runtime::traits::Zero; + use sp_runtime::{ + traits::{CheckedDiv, Zero}, + Saturating, + }; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::pallet] @@ -53,8 +59,23 @@ pub mod pallet { type WeightInfo: WeightInfo; /// The bonding balance. - type Currency: LockableCurrency> - + InspectLockableCurrency; + type Currency: LockableCurrency< + Self::AccountId, + Moment = BlockNumberFor, + Balance = Self::CurrencyBalance, + > + InspectLockableCurrency; + + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to + /// `CheckedDiv`. + type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + + parity_scale_codec::FullCodec + + Copy + + MaybeSerializeDeserialize + + sp_std::fmt::Debug + + Default + + CheckedDiv + + TypeInfo + + MaxEncodedLen; #[pallet::constant] type MomentsPerDay: Get; @@ -64,6 +85,7 @@ pub mod pallet { #[pallet::generate_deposit(pub (super) fn deposit_event)] pub enum Event { Bonded { account: T::AccountId, amount: BalanceOf }, + Unbonded { account: T::AccountId, amount: BalanceOf }, } #[pallet::error] @@ -72,14 +94,16 @@ pub mod pallet { AlreadyBonded, /// Insufficient bond InsufficientBond, + /// account has no bond + NoBond, /// Some corruption in internal state. BadState, } /// Lazy #[pallet::storage] - #[pallet::getter(fn teerdays)] - pub type TeerDays = + #[pallet::getter(fn teerday_bonds)] + pub type TeerDayBonds = StorageMap<_, Blake2_128Concat, T::AccountId, TeerDayBondOf, OptionQuery>; #[pallet::hooks] @@ -88,13 +112,13 @@ pub mod pallet { #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight(::WeightInfo::bond())] + #[pallet::weight(< T as Config >::WeightInfo::bond())] pub fn bond( origin: OriginFor, #[pallet::compact] value: BalanceOf, ) -> DispatchResult { let signer = ensure_signed(origin)?; - ensure!(!TeerDays::::contains_key(&signer), Error::::AlreadyBonded); + ensure!(!TeerDayBonds::::contains_key(&signer), Error::::AlreadyBonded); ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientBond); frame_system::Pallet::::inc_consumers(&signer).map_err(|_| Error::::BadState)?; @@ -106,14 +130,82 @@ pub mod pallet { let teerday_bond = TeerDayBondOf:: { bond: value, last_updated: pallet_timestamp::Pallet::::get(), - accumulated_teerdays: BalanceOf::::zero(), + accumulated_tokentime: BalanceOf::::zero(), + }; + TeerDayBonds::::insert(&signer, teerday_bond); + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(< T as Config >::WeightInfo::unbond())] + pub fn unbond( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + ) -> DispatchResult { + let signer = ensure_signed(origin)?; + + let bond = TeerDayBonds::::get(&signer).ok_or(Error::::NoBond)?; + let now = pallet_timestamp::Pallet::::get(); + let bond = bond.update(now); + + let new_bonded_amount = bond.bond.saturating_sub(value); + + // burn tokentime pro rata + let new_tokentime = bond + .accumulated_tokentime + .checked_div(&bond.bond) + .unwrap_or_default() + .saturating_mul(new_bonded_amount); + + let new_bond = TeerDayBondOf:: { + bond: new_bonded_amount, + last_updated: now, + accumulated_tokentime: new_tokentime, }; - TeerDays::::insert(&signer, teerday_bond); + + if new_bond.bond < T::Currency::minimum_balance() { + TeerDayBonds::::remove(&signer); + T::Currency::remove_lock(TEERDAYS_ID, &signer); + frame_system::Pallet::::dec_consumers(&signer); + } else { + TeerDayBonds::::insert(&signer, new_bond); + T::Currency::set_lock(TEERDAYS_ID, &signer, new_bond.bond, WithdrawReasons::all()); + } + Self::deposit_event(Event::::Unbonded { account: signer.clone(), amount: value }); + Ok(()) + } + + #[pallet::call_index(3)] + #[pallet::weight(< T as Config >::WeightInfo::unbond())] + pub fn update_other(origin: OriginFor, who: T::AccountId) -> DispatchResult { + let signer = ensure_signed(origin)?; + let bond = TeerDayBonds::::get(&signer).ok_or(Error::::NoBond)?; + let now = pallet_timestamp::Pallet::::get(); + let bond = bond.update(now); + TeerDayBonds::::insert(&signer, bond); Ok(()) } } } +/* +impl Pallet { + fn do_update_teerdays(account: T::AccountId) -> DispatchResult { + let bond = TeerDayBonds::::get(&account).ok_or(Error::::NoBond)?; + let new_bond = bond.update(now); + TeerDayBonds::::insert(account, new_bond); + Ok(()) + } + fn do_withdraw_unbonded(controller: &T::AccountId, num_slashing_spans: u32) -> DispatchResult { + let current_bond = crate::TeerDayBonds(controller).map_err(|_| Error::::NoBond)?; + let free_balance = T::Currency::free_balance(controller); + let value = current_bond.bond.min(free_balance); + T::Currency::remove_lock(TEERDAYS_ID, controller); + T::Currency::set_lock(TEERDAYS_ID, controller, value, WithdrawReasons::all()); + Ok(()) + } +}*/ + #[cfg(test)] mod mock; #[cfg(test)] diff --git a/teerdays/src/mock.rs b/teerdays/src/mock.rs index 1e8947be..b0ca821d 100644 --- a/teerdays/src/mock.rs +++ b/teerdays/src/mock.rs @@ -87,15 +87,15 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } -pub type Balance = u64; +pub type Balance = u128; parameter_types! { - pub const ExistentialDeposit: u64 = 1; + pub const ExistentialDeposit: u128 = 1; } impl pallet_balances::Config for Test { type MaxLocks = (); - type Balance = u64; + type Balance = Balance; type DustRemoval = (); type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; @@ -131,6 +131,7 @@ impl Config for Test { type MomentsPerDay = MomentsPerDay; type WeightInfo = (); type Currency = Balances; + type CurrencyBalance = Balance; } // This function basically just builds a genesis storage key/value store according to diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index d0db2bac..0d760000 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -1,12 +1,31 @@ use crate::{mock::*, BalanceOf, Event as TeerDaysEvent}; -use frame_support::assert_ok; +use frame_support::{ + assert_ok, + traits::{OnFinalize, OnInitialize}, +}; use sp_keyring::AccountKeyring; +use sp_runtime::Saturating; + +pub fn run_to_block(n: u32) { + while System::block_number() < n { + if System::block_number() > 1 { + System::on_finalize(System::block_number()); + } + Timestamp::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + } +} + +pub fn set_timestamp(t: u64) { + let _ = pallet_timestamp::Pallet::::set(RuntimeOrigin::none(), t); +} + #[test] fn bonding_works() { new_test_ext().execute_with(|| { let now: Moment = 42; - pallet_timestamp::Pallet::::set(RuntimeOrigin::none(), now) - .expect("set timestamp should work"); + set_timestamp(now); let alice = AccountKeyring::Alice.to_account_id(); let amount: BalanceOf = 10_000_000_000_000; assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); @@ -15,10 +34,80 @@ fn bonding_works() { RuntimeEvent::TeerDays(TeerDaysEvent::Bonded { account: alice.clone(), amount }); assert!(System::events().iter().any(|a| a.event == expected_event)); - let teerdays = - TeerDays::teerdays(&alice).expect("TeerDays entry for bonded account should exist"); + let teerdays = TeerDays::teerday_bonds(&alice) + .expect("TeerDays entry for bonded account should exist"); + assert_eq!(teerdays.bond, amount); + assert_eq!(teerdays.accumulated_tokentime, 0); + assert_eq!(teerdays.last_updated, now); + + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 2); + assert_eq!(account_info.data.frozen, amount); + }) +} +#[test] +fn unbonding_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + let now: Moment = 42; + set_timestamp(now); + let alice = AccountKeyring::Alice.to_account_id(); + let amount: BalanceOf = 10_000_000_000_000; + assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); + + run_to_block(2); + let now = now + MomentsPerDay::get(); + set_timestamp(now); + + let tokentime_accumulated = amount.saturating_mul(MomentsPerDay::get() as Balance); + + let unbond_amount = amount / 3; + assert_ok!(TeerDays::unbond(RuntimeOrigin::signed(alice.clone()), unbond_amount)); + + let expected_event = RuntimeEvent::TeerDays(TeerDaysEvent::Unbonded { + account: alice.clone(), + amount: unbond_amount, + }); + assert!(System::events().iter().any(|a| a.event == expected_event)); + + let teerdays = TeerDays::teerday_bonds(&alice) + .expect("TeerDays entry for bonded account should exist"); + assert_eq!(teerdays.bond, amount - unbond_amount); + assert_eq!( + teerdays.accumulated_tokentime, + tokentime_accumulated.saturating_mul(amount - unbond_amount) / amount + ); + + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 2); + assert_eq!(account_info.data.frozen, amount - unbond_amount); + }) +} + +#[test] +fn update_other_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + let now: Moment = 42; + set_timestamp(now); + let alice = AccountKeyring::Alice.to_account_id(); + let amount: BalanceOf = 10_000_000_000_000; + assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); + + run_to_block(2); + + let now = now + MomentsPerDay::get(); + set_timestamp(now); + + assert_ok!(TeerDays::update_other(RuntimeOrigin::signed(alice.clone()), alice.clone())); + + let teerdays = TeerDays::teerday_bonds(&alice) + .expect("TeerDays entry for bonded account should exist"); assert_eq!(teerdays.bond, amount); - assert_eq!(teerdays.accumulated_teerdays, 0); assert_eq!(teerdays.last_updated, now); + assert_eq!( + teerdays.accumulated_tokentime, + amount.saturating_mul(MomentsPerDay::get() as Balance) + ); }) } diff --git a/teerdays/src/weights.rs b/teerdays/src/weights.rs index 2fa30d82..86fb092c 100644 --- a/teerdays/src/weights.rs +++ b/teerdays/src/weights.rs @@ -24,6 +24,7 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_teerdays. pub trait WeightInfo { fn bond() -> Weight; + fn unbond() -> Weight; } /// Weights for pallet_sidechain using the Integritee parachain node and recommended hardware. @@ -34,6 +35,11 @@ impl WeightInfo for IntegriteeWeight { .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } + fn unbond() -> Weight { + Weight::from_parts(46_200_000, 0u64) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } } // For tests @@ -43,4 +49,9 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(2)) } + fn unbond() -> Weight { + Weight::from_parts(46_200_000, 0u64) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(2)) + } } From ce44373e2f9f85cdd38314e75824f3463a5cade1 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Wed, 3 Jul 2024 21:53:20 +0200 Subject: [PATCH 03/24] fix type trouble --- primitives/teerdays/src/lib.rs | 6 ++---- teerdays/src/lib.rs | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/primitives/teerdays/src/lib.rs b/primitives/teerdays/src/lib.rs index 1ccea675..05c29d82 100644 --- a/primitives/teerdays/src/lib.rs +++ b/primitives/teerdays/src/lib.rs @@ -17,12 +17,10 @@ pub struct TeerDayBond { impl TeerDayBond where Moment: Clone + Copy + Encode + Decode + Saturating, - Balance: AtLeast32BitUnsigned + Clone + Copy + Encode + Decode + Default, + Balance: AtLeast32BitUnsigned + Clone + Copy + Encode + Decode + Default + From, { pub fn update(self, now: Moment) -> Self { - let elapsed = now.saturating_sub(self.last_updated); - let elapsed_enc = Encode::encode(&elapsed); - let elapsed = Balance::decode(&mut &elapsed_enc[..]).unwrap(); //fixme! + let elapsed: Balance = now.saturating_sub(self.last_updated).into(); let new_tokentime = self.accumulated_tokentime.saturating_add(self.bond.saturating_mul(elapsed)); Self { bond: self.bond, last_updated: now, accumulated_tokentime: new_tokentime } diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index f9fe9279..748fd365 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -66,7 +66,7 @@ pub mod pallet { > + InspectLockableCurrency; /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to - /// `CheckedDiv`. + /// `CheckedDiv` and `From`. type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + parity_scale_codec::FullCodec + Copy @@ -74,6 +74,7 @@ pub mod pallet { + sp_std::fmt::Debug + Default + CheckedDiv + + From + TypeInfo + MaxEncodedLen; From 0fc8c79555a1da9aab65d401652235af611651c7 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Thu, 4 Jul 2024 13:45:01 +0200 Subject: [PATCH 04/24] fix unbonding --- primitives/teerdays/src/lib.rs | 7 +++--- teerdays/src/lib.rs | 44 ++++++++++++++++------------------ teerdays/src/tests.rs | 21 +++++++++++++--- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/primitives/teerdays/src/lib.rs b/primitives/teerdays/src/lib.rs index 05c29d82..ddad77be 100644 --- a/primitives/teerdays/src/lib.rs +++ b/primitives/teerdays/src/lib.rs @@ -1,10 +1,9 @@ -use parity_scale_codec::{Compact, Decode, Encode}; +use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, UniqueSaturatedFrom}, - SaturatedConversion, Saturating, + traits::{AtLeast32BitUnsigned}, + Saturating, }; -use std::ops::Mul; #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Default, sp_core::RuntimeDebug, TypeInfo)] pub struct TeerDayBond { diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 748fd365..6ef23a40 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -21,10 +21,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - pallet_prelude::DispatchResult, - traits::{Currency, LockIdentifier}, -}; +use frame_support::traits::{Currency, LockIdentifier}; pub use pallet::*; use teerdays_primitives::TeerDayBond; @@ -87,6 +84,7 @@ pub mod pallet { pub enum Event { Bonded { account: T::AccountId, amount: BalanceOf }, Unbonded { account: T::AccountId, amount: BalanceOf }, + BondUpdated { account: T::AccountId, bond: TeerDayBondOf }, } #[pallet::error] @@ -122,8 +120,6 @@ pub mod pallet { ensure!(!TeerDayBonds::::contains_key(&signer), Error::::AlreadyBonded); ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientBond); - frame_system::Pallet::::inc_consumers(&signer).map_err(|_| Error::::BadState)?; - let free_balance = T::Currency::free_balance(&signer); let value = value.min(free_balance); Self::deposit_event(Event::::Bonded { account: signer.clone(), amount: value }); @@ -145,9 +141,8 @@ pub mod pallet { ) -> DispatchResult { let signer = ensure_signed(origin)?; - let bond = TeerDayBonds::::get(&signer).ok_or(Error::::NoBond)?; - let now = pallet_timestamp::Pallet::::get(); - let bond = bond.update(now); + let bond = Self::do_update_teerdays(&signer)?; + let now = bond.last_updated; let new_bonded_amount = bond.bond.saturating_sub(value); @@ -167,7 +162,6 @@ pub mod pallet { if new_bond.bond < T::Currency::minimum_balance() { TeerDayBonds::::remove(&signer); T::Currency::remove_lock(TEERDAYS_ID, &signer); - frame_system::Pallet::::dec_consumers(&signer); } else { TeerDayBonds::::insert(&signer, new_bond); T::Currency::set_lock(TEERDAYS_ID, &signer, new_bond.bond, WithdrawReasons::all()); @@ -179,24 +173,28 @@ pub mod pallet { #[pallet::call_index(3)] #[pallet::weight(< T as Config >::WeightInfo::unbond())] pub fn update_other(origin: OriginFor, who: T::AccountId) -> DispatchResult { - let signer = ensure_signed(origin)?; - let bond = TeerDayBonds::::get(&signer).ok_or(Error::::NoBond)?; - let now = pallet_timestamp::Pallet::::get(); - let bond = bond.update(now); - TeerDayBonds::::insert(&signer, bond); + let _signer = ensure_signed(origin)?; + let _bond = Self::do_update_teerdays(&who)?; Ok(()) } } } -/* impl Pallet { - fn do_update_teerdays(account: T::AccountId) -> DispatchResult { - let bond = TeerDayBonds::::get(&account).ok_or(Error::::NoBond)?; - let new_bond = bond.update(now); - TeerDayBonds::::insert(account, new_bond); - Ok(()) + /// accumulates pending tokentime and updates state + /// bond must exist + /// returns the updated bond and deposits an event `BondUpdated` + fn do_update_teerdays( + account: &T::AccountId, + ) -> Result, sp_runtime::DispatchError> { + let bond = TeerDayBonds::::get(account).ok_or(Error::::NoBond)?; + let now = pallet_timestamp::Pallet::::get(); + let bond = bond.update(now); + TeerDayBonds::::insert(account, bond); + Self::deposit_event(Event::::BondUpdated { account: account.clone(), bond }); + Ok(bond) } + /* fn do_withdraw_unbonded(controller: &T::AccountId, num_slashing_spans: u32) -> DispatchResult { let current_bond = crate::TeerDayBonds(controller).map_err(|_| Error::::NoBond)?; let free_balance = T::Currency::free_balance(controller); @@ -204,8 +202,8 @@ impl Pallet { T::Currency::remove_lock(TEERDAYS_ID, controller); T::Currency::set_lock(TEERDAYS_ID, controller, value, WithdrawReasons::all()); Ok(()) - } -}*/ + }*/ +} #[cfg(test)] mod mock; diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index 0d760000..b5ac5f72 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -4,7 +4,6 @@ use frame_support::{ traits::{OnFinalize, OnInitialize}, }; use sp_keyring::AccountKeyring; -use sp_runtime::Saturating; pub fn run_to_block(n: u32) { while System::block_number() < n { @@ -41,7 +40,7 @@ fn bonding_works() { assert_eq!(teerdays.last_updated, now); let account_info = System::account(&alice); - assert_eq!(account_info.consumers, 2); + assert_eq!(account_info.consumers, 1); assert_eq!(account_info.data.frozen, amount); }) } @@ -52,6 +51,11 @@ fn unbonding_works() { let now: Moment = 42; set_timestamp(now); let alice = AccountKeyring::Alice.to_account_id(); + + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 0); + assert_eq!(account_info.data.frozen, 0); + let amount: BalanceOf = 10_000_000_000_000; assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); @@ -79,8 +83,19 @@ fn unbonding_works() { ); let account_info = System::account(&alice); - assert_eq!(account_info.consumers, 2); + assert_eq!(account_info.consumers, 1); assert_eq!(account_info.data.frozen, amount - unbond_amount); + + run_to_block(3); + let now = now + MomentsPerDay::get(); + set_timestamp(now); + + // unbond more than we have -> should saturate + assert_ok!(TeerDays::unbond(RuntimeOrigin::signed(alice.clone()), amount)); + assert!(TeerDays::teerday_bonds(&alice).is_none()); + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 0); + assert_eq!(account_info.data.frozen, 0); }) } From 8241c07524171535faff577c1b9aacbee3335270 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Thu, 4 Jul 2024 15:02:45 +0200 Subject: [PATCH 05/24] cosmetics --- primitives/teerdays/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/primitives/teerdays/src/lib.rs b/primitives/teerdays/src/lib.rs index ddad77be..e2b95fa6 100644 --- a/primitives/teerdays/src/lib.rs +++ b/primitives/teerdays/src/lib.rs @@ -1,9 +1,6 @@ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; -use sp_runtime::{ - traits::{AtLeast32BitUnsigned}, - Saturating, -}; +use sp_runtime::{traits::AtLeast32BitUnsigned, Saturating}; #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Default, sp_core::RuntimeDebug, TypeInfo)] pub struct TeerDayBond { From cef5b3feb3ec70a97a66489baa3599c7473b9eed Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Thu, 4 Jul 2024 16:00:46 +0200 Subject: [PATCH 06/24] implement unlock period --- teerdays/src/lib.rs | 81 ++++++++++++++++++++++++++++++++--------- teerdays/src/mock.rs | 2 + teerdays/src/tests.rs | 30 +++++++++++++-- teerdays/src/weights.rs | 19 ++++++++++ 4 files changed, 110 insertions(+), 22 deletions(-) diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 6ef23a40..b64ad08e 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -21,8 +21,11 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{Currency, LockIdentifier}; +use frame_support::traits::{ + Currency, InspectLockableCurrency, LockIdentifier, LockableCurrency, WithdrawReasons, +}; pub use pallet::*; +use sp_runtime::Saturating; use teerdays_primitives::TeerDayBond; pub(crate) const TEERDAYS_ID: LockIdentifier = *b"teerdays"; @@ -77,6 +80,9 @@ pub mod pallet { #[pallet::constant] type MomentsPerDay: Get; + + #[pallet::constant] + type UnlockPeriod: Get; } #[pallet::event] @@ -84,7 +90,8 @@ pub mod pallet { pub enum Event { Bonded { account: T::AccountId, amount: BalanceOf }, Unbonded { account: T::AccountId, amount: BalanceOf }, - BondUpdated { account: T::AccountId, bond: TeerDayBondOf }, + TokenTimeUpdated { account: T::AccountId, bond: TeerDayBondOf }, + Withdrawn { account: T::AccountId, amount: BalanceOf }, } #[pallet::error] @@ -95,6 +102,10 @@ pub mod pallet { InsufficientBond, /// account has no bond NoBond, + /// Can't unbond while unlock is pending + PendingUnlock, + /// no unlock is pending + NotUnlocking, /// Some corruption in internal state. BadState, } @@ -105,6 +116,11 @@ pub mod pallet { pub type TeerDayBonds = StorageMap<_, Blake2_128Concat, T::AccountId, TeerDayBondOf, OptionQuery>; + #[pallet::storage] + #[pallet::getter(fn pending_unlock)] + pub type PendingUnlock = + StorageMap<_, Blake2_128Concat, T::AccountId, (T::Moment, BalanceOf), OptionQuery>; + #[pallet::hooks] impl Hooks> for Pallet {} @@ -140,11 +156,14 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResult { let signer = ensure_signed(origin)?; - + ensure!(Self::pending_unlock(&signer).is_none(), Error::::PendingUnlock); + ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientBond); + ensure!(TeerDayBonds::::contains_key(&signer), Error::::NoBond); let bond = Self::do_update_teerdays(&signer)?; let now = bond.last_updated; let new_bonded_amount = bond.bond.saturating_sub(value); + let unbonded_amount = bond.bond.saturating_sub(new_bonded_amount); // burn tokentime pro rata let new_tokentime = bond @@ -161,22 +180,33 @@ pub mod pallet { if new_bond.bond < T::Currency::minimum_balance() { TeerDayBonds::::remove(&signer); - T::Currency::remove_lock(TEERDAYS_ID, &signer); } else { TeerDayBonds::::insert(&signer, new_bond); - T::Currency::set_lock(TEERDAYS_ID, &signer, new_bond.bond, WithdrawReasons::all()); } - Self::deposit_event(Event::::Unbonded { account: signer.clone(), amount: value }); + PendingUnlock::::insert(&signer, (now + T::UnlockPeriod::get(), unbonded_amount)); + + Self::deposit_event(Event::::Unbonded { + account: signer.clone(), + amount: unbonded_amount, + }); Ok(()) } #[pallet::call_index(3)] - #[pallet::weight(< T as Config >::WeightInfo::unbond())] + #[pallet::weight(< T as Config >::WeightInfo::update_other())] pub fn update_other(origin: OriginFor, who: T::AccountId) -> DispatchResult { let _signer = ensure_signed(origin)?; let _bond = Self::do_update_teerdays(&who)?; Ok(()) } + + #[pallet::call_index(4)] + #[pallet::weight(< T as Config >::WeightInfo::withdraw_unbonded())] + pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { + let signer = ensure_signed(origin)?; + let _success = Self::maybe_withdraw_unbonded(&signer)?; + Ok(()) + } } } @@ -187,22 +217,37 @@ impl Pallet { fn do_update_teerdays( account: &T::AccountId, ) -> Result, sp_runtime::DispatchError> { - let bond = TeerDayBonds::::get(account).ok_or(Error::::NoBond)?; + let bond = Self::teerday_bonds(account).ok_or(Error::::NoBond)?; let now = pallet_timestamp::Pallet::::get(); let bond = bond.update(now); TeerDayBonds::::insert(account, bond); - Self::deposit_event(Event::::BondUpdated { account: account.clone(), bond }); + Self::deposit_event(Event::::TokenTimeUpdated { account: account.clone(), bond }); Ok(bond) } - /* - fn do_withdraw_unbonded(controller: &T::AccountId, num_slashing_spans: u32) -> DispatchResult { - let current_bond = crate::TeerDayBonds(controller).map_err(|_| Error::::NoBond)?; - let free_balance = T::Currency::free_balance(controller); - let value = current_bond.bond.min(free_balance); - T::Currency::remove_lock(TEERDAYS_ID, controller); - T::Currency::set_lock(TEERDAYS_ID, controller, value, WithdrawReasons::all()); - Ok(()) - }*/ + + fn maybe_withdraw_unbonded(account: &T::AccountId) -> Result { + if let Some((due, amount)) = Self::pending_unlock(account) { + let now = pallet_timestamp::Pallet::::get(); + if now < due { + return Err(Error::::PendingUnlock.into()) + } + let locked = T::Currency::balance_locked(TEERDAYS_ID, account); + if amount >= locked { + T::Currency::remove_lock(TEERDAYS_ID, account); + } else { + T::Currency::set_lock( + TEERDAYS_ID, + account, + locked.saturating_sub(amount), + WithdrawReasons::all(), + ); + } + PendingUnlock::::remove(account); + Self::deposit_event(Event::::Withdrawn { account: account.clone(), amount }); + return Ok(true) + } + Err(Error::::NotUnlocking.into()) + } } #[cfg(test)] diff --git a/teerdays/src/mock.rs b/teerdays/src/mock.rs index b0ca821d..6a57cbd8 100644 --- a/teerdays/src/mock.rs +++ b/teerdays/src/mock.rs @@ -124,11 +124,13 @@ impl pallet_timestamp::Config for Test { parameter_types! { pub const MomentsPerDay: u64 = 86_400_000; // [ms/d] + pub const UnlockPeriod: u64 = 86_400_000; // [ms] } impl Config for Test { type RuntimeEvent = RuntimeEvent; type MomentsPerDay = MomentsPerDay; + type UnlockPeriod = MomentsPerDay; type WeightInfo = (); type Currency = Balances; type CurrencyBalance = Balance; diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index b5ac5f72..9e100c4f 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -1,6 +1,6 @@ -use crate::{mock::*, BalanceOf, Event as TeerDaysEvent}; +use crate::{mock::*, BalanceOf, Error, Event as TeerDaysEvent}; use frame_support::{ - assert_ok, + assert_noop, assert_ok, traits::{OnFinalize, OnInitialize}, }; use sp_keyring::AccountKeyring; @@ -45,7 +45,7 @@ fn bonding_works() { }) } #[test] -fn unbonding_works() { +fn unbonding_and_delayed_withdraw_works() { new_test_ext().execute_with(|| { run_to_block(1); let now: Moment = 42; @@ -82,17 +82,39 @@ fn unbonding_works() { tokentime_accumulated.saturating_mul(amount - unbond_amount) / amount ); + // can't unbond again + assert_noop!( + TeerDays::unbond(RuntimeOrigin::signed(alice.clone()), unbond_amount), + Error::::PendingUnlock + ); + // withdrawing not yet possible. fails silently + assert_noop!( + TeerDays::withdraw_unbonded(RuntimeOrigin::signed(alice.clone())), + Error::::PendingUnlock + ); + + run_to_block(3); + let now = now + UnlockPeriod::get(); + set_timestamp(now); + assert_ok!(TeerDays::withdraw_unbonded(RuntimeOrigin::signed(alice.clone()))); + let account_info = System::account(&alice); assert_eq!(account_info.consumers, 1); assert_eq!(account_info.data.frozen, amount - unbond_amount); - run_to_block(3); + run_to_block(4); let now = now + MomentsPerDay::get(); set_timestamp(now); // unbond more than we have -> should saturate assert_ok!(TeerDays::unbond(RuntimeOrigin::signed(alice.clone()), amount)); assert!(TeerDays::teerday_bonds(&alice).is_none()); + + run_to_block(5); + let now = now + UnlockPeriod::get(); + set_timestamp(now); + assert_ok!(TeerDays::withdraw_unbonded(RuntimeOrigin::signed(alice.clone()))); + let account_info = System::account(&alice); assert_eq!(account_info.consumers, 0); assert_eq!(account_info.data.frozen, 0); diff --git a/teerdays/src/weights.rs b/teerdays/src/weights.rs index 86fb092c..b8b86da5 100644 --- a/teerdays/src/weights.rs +++ b/teerdays/src/weights.rs @@ -25,6 +25,8 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn bond() -> Weight; fn unbond() -> Weight; + fn update_other() -> Weight; + fn withdraw_unbonded() -> Weight; } /// Weights for pallet_sidechain using the Integritee parachain node and recommended hardware. @@ -40,6 +42,16 @@ impl WeightInfo for IntegriteeWeight { .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } + + fn update_other() -> Weight { + Weight::from_parts(46_200_000, 0u64) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + + fn withdraw_unbonded() -> Weight { + todo!() + } } // For tests @@ -54,4 +66,11 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(2)) } + fn update_other() -> Weight { + todo!() + } + + fn withdraw_unbonded() -> Weight { + todo!() + } } From 07eea8f122ece2c896a43de17351a0825b873956 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 5 Jul 2024 10:06:18 +0200 Subject: [PATCH 07/24] minimal benchmarks --- Cargo.lock | 2 + teerdays/Cargo.toml | 15 +++++++ teerdays/src/benchmarking.rs | 81 ++++++++++++++++++++++++++++++++++++ teerdays/src/lib.rs | 1 + teerdays/src/weights.rs | 20 +++------ 5 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 teerdays/src/benchmarking.rs diff --git a/Cargo.lock b/Cargo.lock index 94b64111..6e44a83b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2676,8 +2676,10 @@ name = "pallet-teerdays" version = "0.1.0" dependencies = [ "env_logger", + "frame-benchmarking", "frame-support", "frame-system", + "hex-literal", "log", "pallet-balances", "pallet-timestamp", diff --git a/teerdays/Cargo.toml b/teerdays/Cargo.toml index eeda3b2d..778a5f2b 100644 --- a/teerdays/Cargo.toml +++ b/teerdays/Cargo.toml @@ -25,6 +25,10 @@ sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } +# benchmarking +frame-benchmarking = { workspace = true, optional = true } +hex-literal = { workspace = true, optional = true } + [dev-dependencies] env_logger = { workspace = true } sp-keyring = { workspace = true } @@ -32,6 +36,7 @@ sp-keyring = { workspace = true } [features] default = ["std"] std = [ + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "teerdays-primitives/std", @@ -53,3 +58,13 @@ try-runtime = [ "pallet-balances/try-runtime", "sp-runtime/try-runtime", ] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-balances/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] \ No newline at end of file diff --git a/teerdays/src/benchmarking.rs b/teerdays/src/benchmarking.rs new file mode 100644 index 00000000..c9f50ec3 --- /dev/null +++ b/teerdays/src/benchmarking.rs @@ -0,0 +1,81 @@ +/* + Copyright 2021 Integritee AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! TeerDays pallet benchmarking + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +use super::*; + +use crate::Pallet as TeerDays; +use frame_benchmarking::{account, benchmarks}; +use frame_system::RawOrigin; +use sp_runtime::traits::CheckedConversion; +use sp_std::prelude::*; + +benchmarks! { + where_clause { where T::AccountId: From<[u8; 32]>, T::Hash: From<[u8; 32]> } + bond { + pallet_timestamp::Pallet::::set_timestamp(0u32.into()); + let signer: T::AccountId = account("alice", 1, 1); + T::Currency::make_free_balance_be(&signer, 10_000u32.into()); + }: _(RawOrigin::Signed(signer.clone()), 1_000u32.into()) + verify { + assert!(TeerDays::::teerday_bonds(&signer).is_some()); + } + + unbond { + pallet_timestamp::Pallet::::set_timestamp(0u32.into()); + let signer: T::AccountId = account("alice", 1, 1); + T::Currency::make_free_balance_be(&signer, 10_000u32.into()); + TeerDays::::bond(RawOrigin::Signed(signer.clone()).into(), 1_000u32.into())?; + }: _(RawOrigin::Signed(signer.clone()), 500u32.into()) + verify { + assert!(TeerDays::::teerday_bonds(&signer).is_some()); + } + + update_other { + pallet_timestamp::Pallet::::set_timestamp(0u32.into()); + let signer: T::AccountId = account("alice", 1, 1); + T::Currency::make_free_balance_be(&signer, 10_000u32.into()); + TeerDays::::bond(RawOrigin::Signed(signer.clone()).into(), 1_000u32.into())?; + }: _(RawOrigin::Signed(signer.clone()), signer.clone()) + verify { + assert!(TeerDays::::teerday_bonds(&signer).is_some()); + } + withdraw_unbonded { + pallet_timestamp::Pallet::::set_timestamp(42u32.into()); + let signer: T::AccountId = account("alice", 1, 1); + T::Currency::make_free_balance_be(&signer, 10_000u32.into()); + T::Currency::set_lock(TEERDAYS_ID, &signer, 1_000u32.into(), WithdrawReasons::all()); + PendingUnlock::::insert::<_, (T::Moment, BalanceOf)>(&signer, (42u32.into(), 1_000u32.into())); + }: _(RawOrigin::Signed(signer.clone())) + verify { + assert!(TeerDays::::pending_unlock(&signer).is_none()); + } + +} + +#[cfg(test)] +use crate::{Config, Pallet as PalletModule}; + +#[cfg(test)] +use frame_benchmarking::impl_benchmark_test_suite; +use frame_support::assert_ok; + +#[cfg(test)] +impl_benchmark_test_suite!(PalletModule, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index b64ad08e..8748b9cb 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -250,6 +250,7 @@ impl Pallet { } } +mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] diff --git a/teerdays/src/weights.rs b/teerdays/src/weights.rs index b8b86da5..24a957ad 100644 --- a/teerdays/src/weights.rs +++ b/teerdays/src/weights.rs @@ -33,20 +33,14 @@ pub trait WeightInfo { pub struct IntegriteeWeight(PhantomData); impl WeightInfo for IntegriteeWeight { fn bond() -> Weight { - Weight::from_parts(46_200_000, 0u64) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) + todo!() } fn unbond() -> Weight { - Weight::from_parts(46_200_000, 0u64) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) + todo!() } fn update_other() -> Weight { - Weight::from_parts(46_200_000, 0u64) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) + todo!() } fn withdraw_unbonded() -> Weight { @@ -57,14 +51,10 @@ impl WeightInfo for IntegriteeWeight { // For tests impl WeightInfo for () { fn bond() -> Weight { - Weight::from_parts(46_200_000, 0u64) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(2)) + todo!() } fn unbond() -> Weight { - Weight::from_parts(46_200_000, 0u64) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(2)) + todo!() } fn update_other() -> Weight { todo!() From a78af5813f28620166f689367b351934de4d777d Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 5 Jul 2024 10:22:58 +0200 Subject: [PATCH 08/24] add bond_extra call --- teerdays/src/lib.rs | 25 +++++++++++++++++++++++++ teerdays/src/tests.rs | 38 +++++++++++++++++++++++++++++++++++++- teerdays/src/weights.rs | 3 +-- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 8748b9cb..2b77c0ee 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -149,6 +149,30 @@ pub mod pallet { Ok(()) } + #[pallet::call_index(1)] + #[pallet::weight(< T as Config >::WeightInfo::bond())] + pub fn bond_extra( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + ) -> DispatchResult { + let signer = ensure_signed(origin)?; + ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientBond); + let bond = Self::do_update_teerdays(&signer)?; + + let free_balance = T::Currency::free_balance(&signer); + let value = value.min(free_balance); + let new_bond_value = bond.bond.saturating_add(value); + Self::deposit_event(Event::::Bonded { account: signer.clone(), amount: value }); + T::Currency::set_lock(TEERDAYS_ID, &signer, new_bond_value, WithdrawReasons::all()); + let teerday_bond = TeerDayBondOf:: { + bond: new_bond_value, + last_updated: bond.last_updated, + accumulated_tokentime: bond.accumulated_tokentime, + }; + TeerDayBonds::::insert(&signer, teerday_bond); + Ok(()) + } + #[pallet::call_index(2)] #[pallet::weight(< T as Config >::WeightInfo::unbond())] pub fn unbond( @@ -250,6 +274,7 @@ impl Pallet { } } +#[cfg(feature = "runtime-benchmarks")] mod benchmarking; #[cfg(test)] mod mock; diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index 9e100c4f..dffa31a4 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -21,7 +21,7 @@ pub fn set_timestamp(t: u64) { } #[test] -fn bonding_works() { +fn bond_works() { new_test_ext().execute_with(|| { let now: Moment = 42; set_timestamp(now); @@ -44,6 +44,42 @@ fn bonding_works() { assert_eq!(account_info.data.frozen, amount); }) } + +#[test] +fn bond_extra_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + let now: Moment = 42; + set_timestamp(now); + + let alice = AccountKeyring::Alice.to_account_id(); + let amount: BalanceOf = 10_000_000_000_000; + assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); + + run_to_block(2); + let now = now + 10_000; + set_timestamp(now); + + let extra_amount = amount / 2; + assert_ok!(TeerDays::bond_extra(RuntimeOrigin::signed(alice.clone()), extra_amount)); + + let expected_event = RuntimeEvent::TeerDays(TeerDaysEvent::Bonded { + account: alice.clone(), + amount: extra_amount, + }); + assert!(System::events().iter().any(|a| a.event == expected_event)); + + let teerdays = TeerDays::teerday_bonds(&alice) + .expect("TeerDays entry for bonded account should exist"); + assert_eq!(teerdays.bond, amount + extra_amount); + assert_eq!(teerdays.accumulated_tokentime, amount * 10_000); + assert_eq!(teerdays.last_updated, now); + + let account_info = System::account(&alice); + assert_eq!(account_info.data.frozen, amount + extra_amount); + }) +} + #[test] fn unbonding_and_delayed_withdraw_works() { new_test_ext().execute_with(|| { diff --git a/teerdays/src/weights.rs b/teerdays/src/weights.rs index 24a957ad..fb22cd4c 100644 --- a/teerdays/src/weights.rs +++ b/teerdays/src/weights.rs @@ -16,8 +16,7 @@ */ use frame_support::{ - traits::Get, - weights::{constants::RocksDbWeight, Weight}, + weights::{Weight}, }; use sp_std::marker::PhantomData; From 359d3cc4bd5b92b1598f4ec68eb24d324f9f91b6 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 5 Jul 2024 10:37:20 +0200 Subject: [PATCH 09/24] refactor --- primitives/teerdays/src/lib.rs | 6 +++--- teerdays/src/lib.rs | 37 ++++++++++++++++++++-------------- teerdays/src/tests.rs | 8 ++++---- teerdays/src/weights.rs | 4 +--- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/primitives/teerdays/src/lib.rs b/primitives/teerdays/src/lib.rs index e2b95fa6..2e5e6c2b 100644 --- a/primitives/teerdays/src/lib.rs +++ b/primitives/teerdays/src/lib.rs @@ -4,7 +4,7 @@ use sp_runtime::{traits::AtLeast32BitUnsigned, Saturating}; #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Default, sp_core::RuntimeDebug, TypeInfo)] pub struct TeerDayBond { - pub bond: Balance, + pub value: Balance, pub last_updated: Moment, // the unit here is actually balance * moments. pub accumulated_tokentime: Balance, @@ -18,7 +18,7 @@ where pub fn update(self, now: Moment) -> Self { let elapsed: Balance = now.saturating_sub(self.last_updated).into(); let new_tokentime = - self.accumulated_tokentime.saturating_add(self.bond.saturating_mul(elapsed)); - Self { bond: self.bond, last_updated: now, accumulated_tokentime: new_tokentime } + self.accumulated_tokentime.saturating_add(self.value.saturating_mul(elapsed)); + Self { value: self.value, last_updated: now, accumulated_tokentime: new_tokentime } } } diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 2b77c0ee..9e35a8c3 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -100,6 +100,8 @@ pub mod pallet { AlreadyBonded, /// Insufficient bond InsufficientBond, + /// Insufficient unbond + InsufficientUnbond, /// account has no bond NoBond, /// Can't unbond while unlock is pending @@ -141,7 +143,7 @@ pub mod pallet { Self::deposit_event(Event::::Bonded { account: signer.clone(), amount: value }); T::Currency::set_lock(TEERDAYS_ID, &signer, value, WithdrawReasons::all()); let teerday_bond = TeerDayBondOf:: { - bond: value, + value: value, last_updated: pallet_timestamp::Pallet::::get(), accumulated_tokentime: BalanceOf::::zero(), }; @@ -161,11 +163,11 @@ pub mod pallet { let free_balance = T::Currency::free_balance(&signer); let value = value.min(free_balance); - let new_bond_value = bond.bond.saturating_add(value); + let new_bond_value = bond.value.saturating_add(value); Self::deposit_event(Event::::Bonded { account: signer.clone(), amount: value }); T::Currency::set_lock(TEERDAYS_ID, &signer, new_bond_value, WithdrawReasons::all()); let teerday_bond = TeerDayBondOf:: { - bond: new_bond_value, + value: new_bond_value, last_updated: bond.last_updated, accumulated_tokentime: bond.accumulated_tokentime, }; @@ -181,28 +183,27 @@ pub mod pallet { ) -> DispatchResult { let signer = ensure_signed(origin)?; ensure!(Self::pending_unlock(&signer).is_none(), Error::::PendingUnlock); - ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientBond); - ensure!(TeerDayBonds::::contains_key(&signer), Error::::NoBond); + ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientUnbond); let bond = Self::do_update_teerdays(&signer)?; let now = bond.last_updated; - let new_bonded_amount = bond.bond.saturating_sub(value); - let unbonded_amount = bond.bond.saturating_sub(new_bonded_amount); + let new_bonded_amount = bond.value.saturating_sub(value); + let unbonded_amount = bond.value.saturating_sub(new_bonded_amount); // burn tokentime pro rata let new_tokentime = bond .accumulated_tokentime - .checked_div(&bond.bond) + .checked_div(&bond.value) .unwrap_or_default() .saturating_mul(new_bonded_amount); let new_bond = TeerDayBondOf:: { - bond: new_bonded_amount, + value: new_bonded_amount, last_updated: now, accumulated_tokentime: new_tokentime, }; - if new_bond.bond < T::Currency::minimum_balance() { + if new_bond.value < T::Currency::minimum_balance() { TeerDayBonds::::remove(&signer); } else { TeerDayBonds::::insert(&signer, new_bond); @@ -228,7 +229,11 @@ pub mod pallet { #[pallet::weight(< T as Config >::WeightInfo::withdraw_unbonded())] pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { let signer = ensure_signed(origin)?; - let _success = Self::maybe_withdraw_unbonded(&signer)?; + let unlocked = Self::try_withdraw_unbonded(&signer)?; + Self::deposit_event(Event::::Withdrawn { + account: signer.clone(), + amount: unlocked, + }); Ok(()) } } @@ -249,14 +254,17 @@ impl Pallet { Ok(bond) } - fn maybe_withdraw_unbonded(account: &T::AccountId) -> Result { + fn try_withdraw_unbonded( + account: &T::AccountId, + ) -> Result, sp_runtime::DispatchError> { if let Some((due, amount)) = Self::pending_unlock(account) { let now = pallet_timestamp::Pallet::::get(); if now < due { return Err(Error::::PendingUnlock.into()) } let locked = T::Currency::balance_locked(TEERDAYS_ID, account); - if amount >= locked { + let amount = amount.min(locked); + if amount == locked { T::Currency::remove_lock(TEERDAYS_ID, account); } else { T::Currency::set_lock( @@ -267,8 +275,7 @@ impl Pallet { ); } PendingUnlock::::remove(account); - Self::deposit_event(Event::::Withdrawn { account: account.clone(), amount }); - return Ok(true) + return Ok(amount) } Err(Error::::NotUnlocking.into()) } diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index dffa31a4..85183ced 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -35,7 +35,7 @@ fn bond_works() { let teerdays = TeerDays::teerday_bonds(&alice) .expect("TeerDays entry for bonded account should exist"); - assert_eq!(teerdays.bond, amount); + assert_eq!(teerdays.value, amount); assert_eq!(teerdays.accumulated_tokentime, 0); assert_eq!(teerdays.last_updated, now); @@ -71,7 +71,7 @@ fn bond_extra_works() { let teerdays = TeerDays::teerday_bonds(&alice) .expect("TeerDays entry for bonded account should exist"); - assert_eq!(teerdays.bond, amount + extra_amount); + assert_eq!(teerdays.value, amount + extra_amount); assert_eq!(teerdays.accumulated_tokentime, amount * 10_000); assert_eq!(teerdays.last_updated, now); @@ -112,7 +112,7 @@ fn unbonding_and_delayed_withdraw_works() { let teerdays = TeerDays::teerday_bonds(&alice) .expect("TeerDays entry for bonded account should exist"); - assert_eq!(teerdays.bond, amount - unbond_amount); + assert_eq!(teerdays.value, amount - unbond_amount); assert_eq!( teerdays.accumulated_tokentime, tokentime_accumulated.saturating_mul(amount - unbond_amount) / amount @@ -176,7 +176,7 @@ fn update_other_works() { let teerdays = TeerDays::teerday_bonds(&alice) .expect("TeerDays entry for bonded account should exist"); - assert_eq!(teerdays.bond, amount); + assert_eq!(teerdays.value, amount); assert_eq!(teerdays.last_updated, now); assert_eq!( teerdays.accumulated_tokentime, diff --git a/teerdays/src/weights.rs b/teerdays/src/weights.rs index fb22cd4c..a7cae0dd 100644 --- a/teerdays/src/weights.rs +++ b/teerdays/src/weights.rs @@ -15,9 +15,7 @@ limitations under the License. */ -use frame_support::{ - weights::{Weight}, -}; +use frame_support::weights::Weight; use sp_std::marker::PhantomData; /// Weight functions needed for pallet_teerdays. From 0ecbda62ab47f6d9612eea193f17e6718c566659 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 5 Jul 2024 11:13:23 +0200 Subject: [PATCH 10/24] add unhappy tests --- teerdays/src/lib.rs | 6 +-- teerdays/src/tests.rs | 107 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 9e35a8c3..093651e3 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -143,7 +143,7 @@ pub mod pallet { Self::deposit_event(Event::::Bonded { account: signer.clone(), amount: value }); T::Currency::set_lock(TEERDAYS_ID, &signer, value, WithdrawReasons::all()); let teerday_bond = TeerDayBondOf:: { - value: value, + value, last_updated: pallet_timestamp::Pallet::::get(), accumulated_tokentime: BalanceOf::::zero(), }; @@ -160,9 +160,9 @@ pub mod pallet { let signer = ensure_signed(origin)?; ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientBond); let bond = Self::do_update_teerdays(&signer)?; - let free_balance = T::Currency::free_balance(&signer); - let value = value.min(free_balance); + // free includes the already bonded amount, so we need to subtract it + let value = value.min(free_balance.saturating_sub(bond.value)); let new_bond_value = bond.value.saturating_add(value); Self::deposit_event(Event::::Bonded { account: signer.clone(), amount: value }); T::Currency::set_lock(TEERDAYS_ID, &signer, new_bond_value, WithdrawReasons::all()); diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index 85183ced..2d5f87ba 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -1,7 +1,7 @@ -use crate::{mock::*, BalanceOf, Error, Event as TeerDaysEvent}; +use crate::{mock::*, pallet, BalanceOf, Error, Event as TeerDaysEvent}; use frame_support::{ assert_noop, assert_ok, - traits::{OnFinalize, OnInitialize}, + traits::{Currency, OnFinalize, OnInitialize}, }; use sp_keyring::AccountKeyring; @@ -11,6 +11,7 @@ pub fn run_to_block(n: u32) { System::on_finalize(System::block_number()); } Timestamp::on_finalize(System::block_number()); + System::reset_events(); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); } @@ -45,6 +46,35 @@ fn bond_works() { }) } +#[test] +fn bond_saturates_at_free() { + new_test_ext().execute_with(|| { + let now: Moment = 42; + set_timestamp(now); + let alice = AccountKeyring::Alice.to_account_id(); + let alice_free: BalanceOf = 5_000_000_000_000; + ::Currency::make_free_balance_be(&alice, alice_free); + let amount: BalanceOf = 10_000_000_000_000; + assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); + + let expected_event = RuntimeEvent::TeerDays(TeerDaysEvent::Bonded { + account: alice.clone(), + amount: alice_free, + }); + assert!(System::events().iter().any(|a| a.event == expected_event)); + + let teerdays = TeerDays::teerday_bonds(&alice) + .expect("TeerDays entry for bonded account should exist"); + assert_eq!(teerdays.value, alice_free); + assert_eq!(teerdays.accumulated_tokentime, 0); + assert_eq!(teerdays.last_updated, now); + + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 1); + assert_eq!(account_info.data.frozen, alice_free); + }) +} + #[test] fn bond_extra_works() { new_test_ext().execute_with(|| { @@ -80,6 +110,53 @@ fn bond_extra_works() { }) } +#[test] +fn bond_extra_saturates_at_free_margin() { + new_test_ext().execute_with(|| { + run_to_block(1); + let now: Moment = 42; + set_timestamp(now); + + let alice = AccountKeyring::Alice.to_account_id(); + let alice_free: BalanceOf = 11_000_000_000_000; + ::Currency::make_free_balance_be(&alice, alice_free); + let amount: BalanceOf = 10_000_000_000_000; + assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); + + let teerdays = TeerDays::teerday_bonds(&alice) + .expect("TeerDays entry for bonded account should exist"); + assert_eq!(teerdays.value, amount); + assert_eq!(teerdays.accumulated_tokentime, 0); + assert_eq!(teerdays.last_updated, now); + + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 1); + assert_eq!(account_info.data.frozen, amount); + + run_to_block(2); + let now = now + 10_000; + set_timestamp(now); + + let extra_amount = amount / 2; + assert_ok!(TeerDays::bond_extra(RuntimeOrigin::signed(alice.clone()), extra_amount)); + + let expected_event = RuntimeEvent::TeerDays(TeerDaysEvent::Bonded { + account: alice.clone(), + amount: 1_000_000_000_000, + }); + assert_eq!(System::events().get(1).unwrap().event, expected_event); + + let teerdays = TeerDays::teerday_bonds(&alice) + .expect("TeerDays entry for bonded account should exist"); + assert_eq!(teerdays.value, amount + 1_000_000_000_000); + assert_eq!(teerdays.accumulated_tokentime, amount * 10_000); + assert_eq!(teerdays.last_updated, now); + + let account_info = System::account(&alice); + assert_eq!(account_info.data.frozen, amount + 1_000_000_000_000); + }) +} + #[test] fn unbonding_and_delayed_withdraw_works() { new_test_ext().execute_with(|| { @@ -157,6 +234,32 @@ fn unbonding_and_delayed_withdraw_works() { }) } +#[test] +fn unbonding_saturates_at_bonded() { + new_test_ext().execute_with(|| { + run_to_block(1); + let now: Moment = 42; + set_timestamp(now); + let alice = AccountKeyring::Alice.to_account_id(); + + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 0); + assert_eq!(account_info.data.frozen, 0); + + let amount: BalanceOf = 10_000_000_000_000; + assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); + + let unbond_amount = amount * 2; + assert_ok!(TeerDays::unbond(RuntimeOrigin::signed(alice.clone()), unbond_amount)); + + let expected_event = + RuntimeEvent::TeerDays(TeerDaysEvent::Unbonded { account: alice.clone(), amount }); + assert!(System::events().iter().any(|a| a.event == expected_event)); + assert!(TeerDays::teerday_bonds(&alice).is_none()); + assert_eq!(TeerDays::pending_unlock(&alice).unwrap().1, amount); + }) +} + #[test] fn update_other_works() { new_test_ext().execute_with(|| { From 41cde5061b4f57a49fcb2358d896f4079e6b1ca1 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 5 Jul 2024 11:36:37 +0200 Subject: [PATCH 11/24] pallet code docs --- teerdays/src/lib.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 093651e3..edf3743c 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -18,6 +18,32 @@ //! # Teerdays Pallet //! A pallet which allows bonding native TEER tokens and accumulate TEERdays the longer the tokens are bonded //! TEERdays will serve as a basis for governance and other features in the future +//! +//! ### Terminology +//! - **Bonding**: Locking up TEER tokens for a certain period of time into the future. +//! Bonded TEER tokens are not liquid and appear in "frozen" balance but can still be used for voting in network governance +//! - **Unbonding**: Starting the unlock process of bonded TEER tokens +//! - **TEERdays**: Accumulated time of bonded TEER tokens +//! - **TokenTime**: The technical unit of TEERdays storage: TokenTime = Balance (TEER with its 12 digits) * Moment (Milliseconds) +//! +//! ### Usage Lifecycle +//! +//! 1. Bond TEER tokens: `bond(value)` +//! 2. Increase Bond if you like: `bond_extra(value)` +//! 3. *Use your accumulated TEERdays for governance or other features* +//! 4. Unbond TEER tokens: `unbond(value)`. +//! - unbonding is only possible if no unlock is pending +//! - unbonding burns accumulated TEERdays pro rata bonded amount before and after unbonding +//! 5. wait for `UnlockPeriod` to pass +//! 6. Withdraw unbonded TEER tokens: `withdraw_unbonded()` +//! +//! ### Developer Notes +//! +//! Accumulated TokenTime is updated lazily. This means that the `update_other` function must be called if the +//! total amount of accumulated TEERdays is relevant for i.e. determining the electorate in +//! TEERday-based voting. If necessary, this update can be enforced by other pallets using `do_update_teerdays(account)` +//! Failing to update all bonded accounts may lead to underestimation of total electorate voting power +//! #![cfg_attr(not(feature = "std"), no_std)] @@ -81,6 +107,8 @@ pub mod pallet { #[pallet::constant] type MomentsPerDay: Get; + /// The period of time that must pass before a bond can be unbonded. + /// Must use the same unit which the timestamp pallet uses #[pallet::constant] type UnlockPeriod: Get; } From 41b7b327cdf68a7318dab7af6c8b952d73e73ef5 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 5 Jul 2024 11:44:01 +0200 Subject: [PATCH 12/24] cleanup --- teerdays/src/lib.rs | 14 +++++++------- teerdays/src/mock.rs | 4 +--- teerdays/src/tests.rs | 10 +++++----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index edf3743c..f9bd8623 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -104,9 +104,6 @@ pub mod pallet { + TypeInfo + MaxEncodedLen; - #[pallet::constant] - type MomentsPerDay: Get; - /// The period of time that must pass before a bond can be unbonded. /// Must use the same unit which the timestamp pallet uses #[pallet::constant] @@ -116,9 +113,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub (super) fn deposit_event)] pub enum Event { + /// An account's bond has been increased by an amount Bonded { account: T::AccountId, amount: BalanceOf }, + /// An account's bond has been decreased by an amount Unbonded { account: T::AccountId, amount: BalanceOf }, + /// An account's accumulated tokentime has been updated TokenTimeUpdated { account: T::AccountId, bond: TeerDayBondOf }, + /// An account has successfully withdrawn a previously unbonded amount after unlock period has passed Withdrawn { account: T::AccountId, amount: BalanceOf }, } @@ -140,20 +141,19 @@ pub mod pallet { BadState, } - /// Lazy + /// a store for all active bonds. tokentime is updated lazily #[pallet::storage] #[pallet::getter(fn teerday_bonds)] pub type TeerDayBonds = StorageMap<_, Blake2_128Concat, T::AccountId, TeerDayBondOf, OptionQuery>; + /// a store for all pending unlocks which are awaiting the unlock period to pass. + /// Withdrawal happens lazily and causes entry removal from this store #[pallet::storage] #[pallet::getter(fn pending_unlock)] pub type PendingUnlock = StorageMap<_, Blake2_128Concat, T::AccountId, (T::Moment, BalanceOf), OptionQuery>; - #[pallet::hooks] - impl Hooks> for Pallet {} - #[pallet::call] impl Pallet { #[pallet::call_index(0)] diff --git a/teerdays/src/mock.rs b/teerdays/src/mock.rs index 6a57cbd8..a96e779a 100644 --- a/teerdays/src/mock.rs +++ b/teerdays/src/mock.rs @@ -123,14 +123,12 @@ impl pallet_timestamp::Config for Test { } parameter_types! { - pub const MomentsPerDay: u64 = 86_400_000; // [ms/d] pub const UnlockPeriod: u64 = 86_400_000; // [ms] } impl Config for Test { type RuntimeEvent = RuntimeEvent; - type MomentsPerDay = MomentsPerDay; - type UnlockPeriod = MomentsPerDay; + type UnlockPeriod = UnlockPeriod; type WeightInfo = (); type Currency = Balances; type CurrencyBalance = Balance; diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index 2d5f87ba..b13f8d99 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -173,10 +173,10 @@ fn unbonding_and_delayed_withdraw_works() { assert_ok!(TeerDays::bond(RuntimeOrigin::signed(alice.clone()), amount)); run_to_block(2); - let now = now + MomentsPerDay::get(); + let now = now + UnlockPeriod::get(); set_timestamp(now); - let tokentime_accumulated = amount.saturating_mul(MomentsPerDay::get() as Balance); + let tokentime_accumulated = amount.saturating_mul(UnlockPeriod::get() as Balance); let unbond_amount = amount / 3; assert_ok!(TeerDays::unbond(RuntimeOrigin::signed(alice.clone()), unbond_amount)); @@ -216,7 +216,7 @@ fn unbonding_and_delayed_withdraw_works() { assert_eq!(account_info.data.frozen, amount - unbond_amount); run_to_block(4); - let now = now + MomentsPerDay::get(); + let now = now + UnlockPeriod::get(); set_timestamp(now); // unbond more than we have -> should saturate @@ -272,7 +272,7 @@ fn update_other_works() { run_to_block(2); - let now = now + MomentsPerDay::get(); + let now = now + UnlockPeriod::get(); set_timestamp(now); assert_ok!(TeerDays::update_other(RuntimeOrigin::signed(alice.clone()), alice.clone())); @@ -283,7 +283,7 @@ fn update_other_works() { assert_eq!(teerdays.last_updated, now); assert_eq!( teerdays.accumulated_tokentime, - amount.saturating_mul(MomentsPerDay::get() as Balance) + amount.saturating_mul(UnlockPeriod::get() as Balance) ); }) } From cee427815fe8adbeca7e80f8355004bedf88b687 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 5 Jul 2024 11:45:51 +0200 Subject: [PATCH 13/24] cleanup --- teerdays/src/benchmarking.rs | 2 -- teerdays/src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/teerdays/src/benchmarking.rs b/teerdays/src/benchmarking.rs index c9f50ec3..a115c402 100644 --- a/teerdays/src/benchmarking.rs +++ b/teerdays/src/benchmarking.rs @@ -24,7 +24,6 @@ use super::*; use crate::Pallet as TeerDays; use frame_benchmarking::{account, benchmarks}; use frame_system::RawOrigin; -use sp_runtime::traits::CheckedConversion; use sp_std::prelude::*; benchmarks! { @@ -75,7 +74,6 @@ use crate::{Config, Pallet as PalletModule}; #[cfg(test)] use frame_benchmarking::impl_benchmark_test_suite; -use frame_support::assert_ok; #[cfg(test)] impl_benchmark_test_suite!(PalletModule, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index f9bd8623..6d9efac8 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -189,7 +189,7 @@ pub mod pallet { ensure!(value >= T::Currency::minimum_balance(), Error::::InsufficientBond); let bond = Self::do_update_teerdays(&signer)?; let free_balance = T::Currency::free_balance(&signer); - // free includes the already bonded amount, so we need to subtract it + // free confusingly includes the already bonded amount, so we need to subtract it let value = value.min(free_balance.saturating_sub(bond.value)); let new_bond_value = bond.value.saturating_add(value); Self::deposit_event(Event::::Bonded { account: signer.clone(), amount: value }); From 40773d7d529634dc9db6e243fbbbfafada0c0647 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 5 Jul 2024 11:51:16 +0200 Subject: [PATCH 14/24] doc pimp --- teerdays/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 6d9efac8..eb9d4ca4 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -44,6 +44,16 @@ //! TEERday-based voting. If necessary, this update can be enforced by other pallets using `do_update_teerdays(account)` //! Failing to update all bonded accounts may lead to underestimation of total electorate voting power //! +//! #### Numerical stability +//! Assuming: +//! - Balance is u128, decimals: 12, total supply cap: 10^7 TEER +//! - Moment is u64, unit: ms +//! +//! 100years in milliseconds (Moment) are 42bits +//! 10MTEER with 12 digits are 60bits +//! 100years of total supply locked still fits u128 +//! therefore, it is safe to use the Balance type for TEERdays +//! #![cfg_attr(not(feature = "std"), no_std)] From 7d6503174b0c671b0e28c83249accb11a77fa8c1 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Sat, 13 Jul 2024 11:00:52 +0200 Subject: [PATCH 15/24] taplo --- teerdays/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teerdays/Cargo.toml b/teerdays/Cargo.toml index 778a5f2b..a81958d2 100644 --- a/teerdays/Cargo.toml +++ b/teerdays/Cargo.toml @@ -67,4 +67,4 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", -] \ No newline at end of file +] From 0003b7319d4752948be1f51cd4b07bb7d241d60e Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Sat, 13 Jul 2024 11:02:48 +0200 Subject: [PATCH 16/24] taplo --- primitives/teerdays/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/teerdays/Cargo.toml b/primitives/teerdays/Cargo.toml index 49395ba0..92de8339 100644 --- a/primitives/teerdays/Cargo.toml +++ b/primitives/teerdays/Cargo.toml @@ -22,4 +22,4 @@ std = [ "serde/std", "sp-core/std", "sp-runtime/std", -] \ No newline at end of file +] From 7c15cae91a701f932f4f80c319eeb8f77ef01c9d Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Sat, 13 Jul 2024 11:56:45 +0200 Subject: [PATCH 17/24] zepter+fmt --- rust-toolchain.toml | 2 +- teerdays/Cargo.toml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 317bf8c4..00723b2c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2023-06-01" +channel = "1.75" targets = ["wasm32-unknown-unknown"] profile = "default" # include rustfmt, clippy diff --git a/teerdays/Cargo.toml b/teerdays/Cargo.toml index a81958d2..015d4101 100644 --- a/teerdays/Cargo.toml +++ b/teerdays/Cargo.toml @@ -39,23 +39,25 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "teerdays-primitives/std", "log/std", "pallet-balances/std", + "pallet-timestamp/std", "parity-scale-codec/std", "scale-info/std", "serde/std", "sp-core/std", "sp-io/std", + "sp-keyring/std", "sp-runtime/std", "sp-std/std", - "sp-keyring/std", + "teerdays-primitives/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", + "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", ] From 5e3e46ce470a8570f634c37642f34bf1ecf40768 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Sun, 14 Jul 2024 09:33:29 +0200 Subject: [PATCH 18/24] fmt nightly --- .github/workflows/ci.yml | 5 ++++- rust-toolchain.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5bd5ebc..3f1c00b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,12 +38,15 @@ jobs: cargo test --all --features runtime-benchmarks, cargo test --all --features dot, cargo test --all --features ksm, - cargo fmt --all -- --check, + cargo +nightly fmt --all -- --check, cargo clippy -- -D warnings ] steps: - uses: actions/checkout@v3 + - name: Install nightly toolchain + run: rustup toolchain install nightly --profile minimal --component rustfmt + # With rustup's nice new toml format, we just need to run rustup show to install the toolchain # https://github.com/actions-rs/toolchain/issues/126#issuecomment-782989659 - name: Setup Rust toolchain diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 00723b2c..f7868d17 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.75" +channel = "stable" targets = ["wasm32-unknown-unknown"] profile = "default" # include rustfmt, clippy From eea21f4c3eb0d29c29d83b40263556763e747593 Mon Sep 17 00:00:00 2001 From: Christian Langenbacher Date: Sun, 14 Jul 2024 10:21:49 +0200 Subject: [PATCH 19/24] fmt --- asset-registry/src/lib.rs | 3 ++- primitives/xcm/src/lib.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/asset-registry/src/lib.rs b/asset-registry/src/lib.rs index e5d64356..e5ca8961 100644 --- a/asset-registry/src/lib.rs +++ b/asset-registry/src/lib.rs @@ -155,7 +155,8 @@ pub mod pallet { Some(AccountId32 { .. }) | Some(AccountKey20 { .. }) | Some(PalletInstance(_)) | - Some(Parachain(_)) | None + Some(Parachain(_)) | + None ); check | diff --git a/primitives/xcm/src/lib.rs b/primitives/xcm/src/lib.rs index e832a5e1..9cfb85fc 100644 --- a/primitives/xcm/src/lib.rs +++ b/primitives/xcm/src/lib.rs @@ -115,7 +115,8 @@ impl where + > +where AssetIdInfoGetter: AssetLocationGetter, AssetsPallet: Inspect, BalancesPallet: Currency, From 6386de6083c05a3c928cb008d4792a773d8f775d Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Mon, 15 Jul 2024 16:09:55 +0200 Subject: [PATCH 20/24] clippy --- primitives/teerex/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/teerex/src/lib.rs b/primitives/teerex/src/lib.rs index 228f335b..79520b23 100644 --- a/primitives/teerex/src/lib.rs +++ b/primitives/teerex/src/lib.rs @@ -159,7 +159,7 @@ where MultiEnclave::Sgx(enclave) => match enclave.maybe_pubkey() { Some(pubkey) => AnySigner::from(MultiSigner::from(sp_core::ed25519::Public::from_raw(pubkey))), - None => AnySigner::try_from(enclave.report_data.d).unwrap_or_default(), + None => AnySigner::from(enclave.report_data.d), }, } } From e3b925583d60464296710a3baa48bb88df1a3acb Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Mon, 15 Jul 2024 16:31:35 +0200 Subject: [PATCH 21/24] review fixes --- teerdays/src/lib.rs | 54 ++++++++++++++++++++++++++----------------- teerdays/src/tests.rs | 5 ++-- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index eb9d4ca4..4dbee187 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -166,6 +166,8 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Bond TEER tokens. This will lock the tokens in order to start accumulating TEERdays + /// The minimum bond is the existential deposit #[pallet::call_index(0)] #[pallet::weight(< T as Config >::WeightInfo::bond())] pub fn bond( @@ -189,6 +191,8 @@ pub mod pallet { Ok(()) } + /// Increase an existing bond on the signer's account + /// The minimum additional bond specified by `value` must exceed the existential deposit #[pallet::call_index(1)] #[pallet::weight(< T as Config >::WeightInfo::bond())] pub fn bond_extra( @@ -213,6 +217,11 @@ pub mod pallet { Ok(()) } + /// Decrease an existing bond on the signer's account + /// The minimum unbond specified by `value` must exceed the existential deposit + /// If `value` is equal or greater than the current bond, the bond will be removed + /// The unbonded amount will still be subject to an unbonding period before the amount can be withdrawn + /// Unbonding will burn accumulated TEERdays pro rata. #[pallet::call_index(2)] #[pallet::weight(< T as Config >::WeightInfo::unbond())] pub fn unbond( @@ -255,6 +264,9 @@ pub mod pallet { Ok(()) } + /// Update the accumulated tokentime for an account lazily + /// This can be helpful if other pallets use TEERdays and need to ensure the total + /// accumulated tokentime is up to date. #[pallet::call_index(3)] #[pallet::weight(< T as Config >::WeightInfo::update_other())] pub fn update_other(origin: OriginFor, who: T::AccountId) -> DispatchResult { @@ -263,6 +275,7 @@ pub mod pallet { Ok(()) } + /// Withdraw an unbonded amount after the unbonding period has expired #[pallet::call_index(4)] #[pallet::weight(< T as Config >::WeightInfo::withdraw_unbonded())] pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { @@ -279,7 +292,7 @@ pub mod pallet { impl Pallet { /// accumulates pending tokentime and updates state - /// bond must exist + /// bond must exist or will err. /// returns the updated bond and deposits an event `BondUpdated` fn do_update_teerdays( account: &T::AccountId, @@ -295,27 +308,26 @@ impl Pallet { fn try_withdraw_unbonded( account: &T::AccountId, ) -> Result, sp_runtime::DispatchError> { - if let Some((due, amount)) = Self::pending_unlock(account) { - let now = pallet_timestamp::Pallet::::get(); - if now < due { - return Err(Error::::PendingUnlock.into()) - } - let locked = T::Currency::balance_locked(TEERDAYS_ID, account); - let amount = amount.min(locked); - if amount == locked { - T::Currency::remove_lock(TEERDAYS_ID, account); - } else { - T::Currency::set_lock( - TEERDAYS_ID, - account, - locked.saturating_sub(amount), - WithdrawReasons::all(), - ); - } - PendingUnlock::::remove(account); - return Ok(amount) + let (due, amount) = + Self::pending_unlock(account).ok_or_else(|| Error::::NotUnlocking)?; + let now = pallet_timestamp::Pallet::::get(); + if now < due { + return Err(Error::::PendingUnlock.into()) + } + let locked = T::Currency::balance_locked(TEERDAYS_ID, account); + let amount = amount.min(locked); + if amount == locked { + T::Currency::remove_lock(TEERDAYS_ID, account); + } else { + T::Currency::set_lock( + TEERDAYS_ID, + account, + locked.saturating_sub(amount), + WithdrawReasons::all(), + ); } - Err(Error::::NotUnlocking.into()) + PendingUnlock::::remove(account); + return Ok(amount) } } diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index b13f8d99..1f8dfa7f 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -158,7 +158,7 @@ fn bond_extra_saturates_at_free_margin() { } #[test] -fn unbonding_and_delayed_withdraw_works() { +fn withrawing_unbonded_after_unlock_period_works() { new_test_ext().execute_with(|| { run_to_block(1); let now: Moment = 42; @@ -190,6 +190,7 @@ fn unbonding_and_delayed_withdraw_works() { let teerdays = TeerDays::teerday_bonds(&alice) .expect("TeerDays entry for bonded account should exist"); assert_eq!(teerdays.value, amount - unbond_amount); + // accumulated tokentime is reduced pro-rata assert_eq!( teerdays.accumulated_tokentime, tokentime_accumulated.saturating_mul(amount - unbond_amount) / amount @@ -200,7 +201,7 @@ fn unbonding_and_delayed_withdraw_works() { TeerDays::unbond(RuntimeOrigin::signed(alice.clone()), unbond_amount), Error::::PendingUnlock ); - // withdrawing not yet possible. fails silently + // withdrawing not yet possible. assert_noop!( TeerDays::withdraw_unbonded(RuntimeOrigin::signed(alice.clone())), Error::::PendingUnlock From 9eb407935be6ddf73f2d9b1e4008919794cf8014 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Mon, 15 Jul 2024 16:45:00 +0200 Subject: [PATCH 22/24] clippy again --- claims/src/lib.rs | 2 +- enclave-bridge/src/lib.rs | 2 +- teerdays/src/lib.rs | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/claims/src/lib.rs b/claims/src/lib.rs index df11e96d..e9efd9ec 100644 --- a/claims/src/lib.rs +++ b/claims/src/lib.rs @@ -480,7 +480,7 @@ impl Pallet { } // We first need to deposit the balance to ensure that the account exists. - CurrencyOf::::deposit_creating(&dest, balance_due); + let _ = CurrencyOf::::deposit_creating(&dest, balance_due); // Check if this claim should have a vesting schedule. if let Some(vs) = vesting { diff --git a/enclave-bridge/src/lib.rs b/enclave-bridge/src/lib.rs index 7d64947f..b224eb60 100644 --- a/enclave-bridge/src/lib.rs +++ b/enclave-bridge/src/lib.rs @@ -386,8 +386,8 @@ pub mod pallet { let new_status: ShardSignerStatusVec = Self::shard_status(shard) .ok_or(Error::::ShardNotFound)? .iter() + .filter(|&signer_status| signer_status.signer != subject) .cloned() - .filter(|signer_status| signer_status.signer != subject) .collect::>>() .try_into() .expect("can only become smaller by filtering"); diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 4dbee187..5f8a8281 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -21,7 +21,7 @@ //! //! ### Terminology //! - **Bonding**: Locking up TEER tokens for a certain period of time into the future. -//! Bonded TEER tokens are not liquid and appear in "frozen" balance but can still be used for voting in network governance +//! Bonded TEER tokens are not liquid and appear in "frozen" balance but can still be used for voting in network governance //! - **Unbonding**: Starting the unlock process of bonded TEER tokens //! - **TEERdays**: Accumulated time of bonded TEER tokens //! - **TokenTime**: The technical unit of TEERdays storage: TokenTime = Balance (TEER with its 12 digits) * Moment (Milliseconds) @@ -33,7 +33,7 @@ //! 3. *Use your accumulated TEERdays for governance or other features* //! 4. Unbond TEER tokens: `unbond(value)`. //! - unbonding is only possible if no unlock is pending -//! - unbonding burns accumulated TEERdays pro rata bonded amount before and after unbonding +//! - unbonding burns accumulated TEERdays pro rata bonded amount before and after unbonding //! 5. wait for `UnlockPeriod` to pass //! 6. Withdraw unbonded TEER tokens: `withdraw_unbonded()` //! @@ -308,8 +308,7 @@ impl Pallet { fn try_withdraw_unbonded( account: &T::AccountId, ) -> Result, sp_runtime::DispatchError> { - let (due, amount) = - Self::pending_unlock(account).ok_or_else(|| Error::::NotUnlocking)?; + let (due, amount) = Self::pending_unlock(account).ok_or(Error::::NotUnlocking)?; let now = pallet_timestamp::Pallet::::get(); if now < due { return Err(Error::::PendingUnlock.into()) @@ -327,7 +326,7 @@ impl Pallet { ); } PendingUnlock::::remove(account); - return Ok(amount) + Ok(amount) } } From f2acfbeda7fce4f7ca3c5874faae5109da523ae4 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Mon, 15 Jul 2024 16:48:44 +0200 Subject: [PATCH 23/24] clippy version mismatch? --- teerex/sgx-verify/src/ephemeral_key.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/teerex/sgx-verify/src/ephemeral_key.rs b/teerex/sgx-verify/src/ephemeral_key.rs index 72998af3..3682b438 100644 --- a/teerex/sgx-verify/src/ephemeral_key.rs +++ b/teerex/sgx-verify/src/ephemeral_key.rs @@ -18,6 +18,7 @@ use crate::{utils::length_from_raw_data, CertDer}; use sp_std::convert::TryFrom; +#[allow(dead_code)] pub struct EphemeralKey<'a>(&'a [u8]); pub const PRIME256V1_OID: &[u8; 10] = &[0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]; From 33d48e38929b6ae3212c4420df21dec2726f7115 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Mon, 15 Jul 2024 16:51:46 +0200 Subject: [PATCH 24/24] clippy version mismatch? --- claims/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/claims/src/lib.rs b/claims/src/lib.rs index e9efd9ec..b29c96f7 100644 --- a/claims/src/lib.rs +++ b/claims/src/lib.rs @@ -423,7 +423,7 @@ pub mod pallet { priority: PRIORITY, requires: vec![], provides: vec![("claims", signer).encode()], - longevity: TransactionLongevity::max_value(), + longevity: TransactionLongevity::MAX, propagate: true, }) } @@ -1317,7 +1317,7 @@ mod tests { priority: 100, requires: vec![], provides: vec![("claims", eth(&alice())).encode()], - longevity: TransactionLongevity::max_value(), + longevity: TransactionLongevity::MAX, propagate: true, }) ); @@ -1350,7 +1350,7 @@ mod tests { priority: 100, requires: vec![], provides: vec![("claims", eth(&dave())).encode()], - longevity: TransactionLongevity::max_value(), + longevity: TransactionLongevity::MAX, propagate: true, }) );