diff --git a/teerdays/src/lib.rs b/teerdays/src/lib.rs index 5fda660b..f76591b3 100644 --- a/teerdays/src/lib.rs +++ b/teerdays/src/lib.rs @@ -127,7 +127,7 @@ pub mod pallet { /// 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 }, + Unbonded { account: T::AccountId, amount: BalanceOf, burned_tokentime: 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 @@ -238,6 +238,7 @@ pub mod pallet { let new_bonded_amount = bond.value.saturating_sub(value); let unbonded_amount = bond.value.saturating_sub(new_bonded_amount); + let pre_tokentime = bond.accumulated_tokentime; // burn tokentime pro rata let new_tokentime = bond .accumulated_tokentime @@ -261,6 +262,7 @@ pub mod pallet { Self::deposit_event(Event::::Unbonded { account: signer.clone(), amount: unbonded_amount, + burned_tokentime: pre_tokentime.saturating_sub(new_tokentime), }); Ok(()) } @@ -312,7 +314,7 @@ impl Pallet { 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()) + return Err(Error::::PendingUnlock.into()); } let locked = T::Currency::balance_locked(TEERDAYS_ID, account); let amount = amount.min(locked); diff --git a/teerdays/src/tests.rs b/teerdays/src/tests.rs index b11d9eb7..164e2a75 100644 --- a/teerdays/src/tests.rs +++ b/teerdays/src/tests.rs @@ -1,293 +1,296 @@ use crate::{mock::*, pallet, BalanceOf, Error, Event as TeerDaysEvent}; use frame_support::{ - assert_noop, assert_ok, - traits::{Currency, OnFinalize, OnInitialize}, + assert_noop, assert_ok, + traits::{Currency, OnFinalize, OnInitialize}, }; use sp_keyring::AccountKeyring; 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::reset_events(); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - } + while System::block_number() < n { + if System::block_number() > 1 { + 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()); + } } pub fn set_timestamp(t: u64) { - let _ = pallet_timestamp::Pallet::::set(RuntimeOrigin::none(), t); + let _ = pallet_timestamp::Pallet::::set(RuntimeOrigin::none(), t); } #[test] fn bond_works() { - new_test_ext().execute_with(|| { - let now: Moment = 42; - set_timestamp(now); - let alice = AccountKeyring::Alice.to_account_id(); - let alice_free: BalanceOf = 15_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 }); - 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, 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); - assert_eq!(account_info.data.free, alice_free - amount); - }) + new_test_ext().execute_with(|| { + let now: Moment = 42; + set_timestamp(now); + let alice = AccountKeyring::Alice.to_account_id(); + let alice_free: BalanceOf = 15_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 }); + 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, 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); + }) } #[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); - }) + 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(|| { - 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.value, 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); - }) + 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.value, 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 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); - }) + 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 withrawing_unbonded_after_unlock_period_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 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)); - - run_to_block(2); - let now = now + UnlockPeriod::get(); - set_timestamp(now); - - 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)); - - 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.value, amount - unbond_amount); - // accumulated tokentime is reduced pro-rata - assert_eq!( - teerdays.accumulated_tokentime, - tokentime_accumulated.saturating_mul(amount - unbond_amount) / amount - ); - - // can't unbond again - assert_noop!( +fn withdrawing_unbonded_after_unlock_period_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 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)); + + run_to_block(2); + let now = now + UnlockPeriod::get(); + set_timestamp(now); + + let tokentime_accumulated = amount.saturating_mul(UnlockPeriod::get() as Balance); + + let unbond_amount = amount / 5; + assert_ok!(TeerDays::unbond(RuntimeOrigin::signed(alice.clone()), unbond_amount)); + + let expected_event = RuntimeEvent::TeerDays(TeerDaysEvent::Unbonded { + account: alice.clone(), + amount: unbond_amount, + burned_tokentime: tokentime_accumulated / 5, + }); + 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, amount - unbond_amount); + // accumulated tokentime is reduced pro-rata + assert_eq!( + teerdays.accumulated_tokentime, + 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. - assert_noop!( + // withdrawing not yet possible. + 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()))); + 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); + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 1); + assert_eq!(account_info.data.frozen, amount - unbond_amount); - run_to_block(4); - let now = now + UnlockPeriod::get(); - set_timestamp(now); + run_to_block(4); + let now = now + UnlockPeriod::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()); + // 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()))); + 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); - }) + let account_info = System::account(&alice); + assert_eq!(account_info.consumers, 0); + assert_eq!(account_info.data.frozen, 0); + }) } #[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); - }) + 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, + burned_tokentime: 0, //no time has elapsed + }); + 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(|| { - 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 + UnlockPeriod::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.value, amount); - assert_eq!(teerdays.last_updated, now); - assert_eq!( - teerdays.accumulated_tokentime, - amount.saturating_mul(UnlockPeriod::get() as Balance) - ); - }) + 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 + UnlockPeriod::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.value, amount); + assert_eq!(teerdays.last_updated, now); + assert_eq!( + teerdays.accumulated_tokentime, + amount.saturating_mul(UnlockPeriod::get() as Balance) + ); + }) }