diff --git a/asset-registry/src/mock/para.rs b/asset-registry/src/mock/para.rs index 9a41b838d..c7074fd5a 100644 --- a/asset-registry/src/mock/para.rs +++ b/asset-registry/src/mock/para.rs @@ -96,6 +96,8 @@ impl orml_tokens::Config for Runtime { type MaxReserves = (); type MaxLocks = ConstU32<50>; type DustRemovalWhitelist = Nothing; + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); } #[derive(scale_info::TypeInfo, Encode, Decode, Clone, Eq, PartialEq, Debug)] diff --git a/currencies/src/mock.rs b/currencies/src/mock.rs index 1980b388e..1ef3cf06f 100644 --- a/currencies/src/mock.rs +++ b/currencies/src/mock.rs @@ -85,6 +85,8 @@ impl orml_tokens::Config for Runtime { type MaxReserves = ConstU32<100_000>; type ReserveIdentifier = ReserveIdentifier; type DustRemovalWhitelist = Nothing; + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); } pub const NATIVE_CURRENCY_ID: CurrencyId = 1; diff --git a/payments/src/mock.rs b/payments/src/mock.rs index e26626e89..b31c9283e 100644 --- a/payments/src/mock.rs +++ b/payments/src/mock.rs @@ -104,6 +104,8 @@ impl orml_tokens::Config for Test { type DustRemovalWhitelist = MockDustRemovalWhitelist; type MaxReserves = ConstU32<2>; type ReserveIdentifier = ReserveIdentifier; + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); } pub struct MockDisputeResolver; diff --git a/tokens/src/lib.rs b/tokens/src/lib.rs index c7d73d7dc..06fefa10c 100644 --- a/tokens/src/lib.rs +++ b/tokens/src/lib.rs @@ -67,7 +67,7 @@ use sp_std::{cmp, convert::Infallible, marker, prelude::*, vec::Vec}; use orml_traits::{ arithmetic::{self, Signed}, currency::TransferAll, - BalanceStatus, GetByKey, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, + BalanceStatus, GetByKey, Happened, LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, NamedMultiReservableCurrency, OnDust, }; @@ -216,6 +216,12 @@ pub mod module { /// Handler to burn or transfer account's dust type OnDust: OnDust; + /// Handler for when an account was created + type OnNewTokenAccount: Happened<(Self::AccountId, Self::CurrencyId)>; + + /// Handler for when an account was created + type OnKilledTokenAccount: Happened<(Self::AccountId, Self::CurrencyId)>; + #[pallet::constant] type MaxLocks: Get; @@ -747,9 +753,11 @@ impl Pallet { // Ignore the result, because if it failed then there are remaining consumers, // and the account storage in frame_system shouldn't be reaped. let _ = frame_system::Pallet::::dec_providers(who); + T::OnKilledTokenAccount::happened(&(who.clone(), currency_id)); } else if !existed && exists { // if new, increase account provider frame_system::Pallet::::inc_providers(who); + T::OnNewTokenAccount::happened(&(who.clone(), currency_id)); } if let Some(endowed) = maybe_endowed { diff --git a/tokens/src/mock.rs b/tokens/src/mock.rs index 7be814521..2deeaa52a 100644 --- a/tokens/src/mock.rs +++ b/tokens/src/mock.rs @@ -220,6 +220,51 @@ parameter_type_with_key! { }; } +thread_local! { + pub static CREATED: RefCell> = RefCell::new(vec![]); + pub static KILLED: RefCell> = RefCell::new(vec![]); +} + +pub struct TrackCreatedAccounts; +impl TrackCreatedAccounts { + pub fn accounts() -> Vec<(AccountId, CurrencyId)> { + CREATED.with(|accounts| accounts.borrow().clone()) + } + + pub fn reset() { + CREATED.with(|accounts| { + accounts.replace(vec![]); + }); + } +} +impl Happened<(AccountId, CurrencyId)> for TrackCreatedAccounts { + fn happened((who, currency): &(AccountId, CurrencyId)) { + CREATED.with(|accounts| { + accounts.borrow_mut().push((who.clone(), *currency)); + }); + } +} + +pub struct TrackKilledAccounts; +impl TrackKilledAccounts { + pub fn accounts() -> Vec<(AccountId, CurrencyId)> { + KILLED.with(|accounts| accounts.borrow().clone()) + } + + pub fn reset() { + KILLED.with(|accounts| { + accounts.replace(vec![]); + }); + } +} +impl Happened<(AccountId, CurrencyId)> for TrackKilledAccounts { + fn happened((who, currency): &(AccountId, CurrencyId)) { + KILLED.with(|accounts| { + accounts.borrow_mut().push((who.clone(), *currency)); + }); + } +} + parameter_types! { pub DustReceiver: AccountId = PalletId(*b"orml/dst").into_account(); } @@ -232,6 +277,8 @@ impl Config for Runtime { type WeightInfo = (); type ExistentialDeposits = ExistentialDeposits; type OnDust = TransferDust; + type OnNewTokenAccount = TrackCreatedAccounts; + type OnKilledTokenAccount = TrackKilledAccounts; type MaxLocks = ConstU32<2>; type MaxReserves = ConstU32<2>; type ReserveIdentifier = ReserveIdentifier; @@ -296,6 +343,9 @@ impl ExtBuilder { .unwrap(); } + TrackCreatedAccounts::reset(); + TrackKilledAccounts::reset(); + let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext diff --git a/tokens/src/tests.rs b/tokens/src/tests.rs index 0cbf6ffd3..02b923293 100644 --- a/tokens/src/tests.rs +++ b/tokens/src/tests.rs @@ -55,6 +55,7 @@ fn transfer_should_work() { Error::::ExistentialDeposit, ); assert_ok!(Tokens::transfer(Some(ALICE).into(), CHARLIE, DOT, 2)); + assert_eq!(TrackCreatedAccounts::accounts(), vec![(CHARLIE, DOT)]); // imply AllowDeath assert!(Accounts::::contains_key(ALICE, DOT)); @@ -131,6 +132,7 @@ fn transfer_all_allow_death_should_work() { assert!(Accounts::::contains_key(ALICE, DOT)); assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); assert_ok!(Tokens::transfer_all(Some(ALICE).into(), CHARLIE, DOT, false)); + assert_eq!(TrackCreatedAccounts::accounts(), vec![(CHARLIE, DOT)]); System::assert_last_event(Event::Tokens(crate::Event::Transfer { currency_id: DOT, from: ALICE, @@ -139,6 +141,7 @@ fn transfer_all_allow_death_should_work() { })); assert!(!Accounts::::contains_key(ALICE, DOT)); assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(TrackKilledAccounts::accounts(), vec![(ALICE, DOT)]); assert_ok!(Tokens::set_lock(ID_1, DOT, &BOB, 50)); assert_eq!(Tokens::accounts(&BOB, DOT).frozen, 50); @@ -176,6 +179,7 @@ fn force_transfer_should_work() { amount: 100, })); assert!(!Accounts::::contains_key(ALICE, DOT)); + assert_eq!(TrackKilledAccounts::accounts(), vec![(ALICE, DOT)]); assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); assert_eq!(Tokens::free_balance(DOT, &BOB), 200); }); @@ -1145,3 +1149,21 @@ fn exceeding_max_reserves_should_fail() { ); }); } + +#[test] +fn lifecycle_callbacks_are_activated() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Tokens::set_balance(RawOrigin::Root.into(), ALICE, DOT, 200, 0)); + assert_eq!(TrackCreatedAccounts::accounts(), vec![(ALICE, DOT)]); + + assert_ok!(Tokens::set_balance(RawOrigin::Root.into(), ALICE, BTC, 200, 0)); + assert_eq!(TrackCreatedAccounts::accounts(), vec![(ALICE, DOT), (ALICE, BTC)]); + + assert_ok!(Tokens::transfer_all(Some(ALICE).into(), CHARLIE, BTC, false)); + assert_eq!( + TrackCreatedAccounts::accounts(), + vec![(ALICE, DOT), (ALICE, BTC), (CHARLIE, BTC)] + ); + assert_eq!(TrackKilledAccounts::accounts(), vec![(ALICE, BTC)]); + }) +} diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index b30ccb891..2dde03460 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -87,6 +87,8 @@ impl orml_tokens::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type DustRemovalWhitelist = Everything; + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); } parameter_types! { diff --git a/xtokens/src/mock/para_relative_view.rs b/xtokens/src/mock/para_relative_view.rs index ed4abd521..f6786d09f 100644 --- a/xtokens/src/mock/para_relative_view.rs +++ b/xtokens/src/mock/para_relative_view.rs @@ -90,6 +90,8 @@ impl orml_tokens::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type DustRemovalWhitelist = Everything; + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); } parameter_types! {