Skip to content

Commit 6ca1186

Browse files
Dinonardnathanwhit
authored andcommitted
Pallet assets improvements (paritytech#13543)
1 parent b737f90 commit 6ca1186

File tree

4 files changed

+85
-18
lines changed

4 files changed

+85
-18
lines changed

frame/assets/src/functions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,8 +661,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
661661
status: AssetStatus::Live,
662662
},
663663
);
664+
ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::<T, I>::CallbackFailed);
664665
Self::deposit_event(Event::ForceCreated { asset_id: id, owner: owner.clone() });
665-
T::CallbackHandle::created(&id, &owner);
666666
Ok(())
667667
}
668668

@@ -752,7 +752,6 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
752752
approvals_destroyed: removed_approvals as u32,
753753
approvals_remaining: details.approvals as u32,
754754
});
755-
T::CallbackHandle::destroyed(&id);
756755
Ok(())
757756
})?;
758757
Ok(removed_approvals)
@@ -767,6 +766,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
767766
ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
768767
ensure!(details.accounts == 0, Error::<T, I>::InUse);
769768
ensure!(details.approvals == 0, Error::<T, I>::InUse);
769+
ensure!(T::CallbackHandle::destroyed(&id).is_ok(), Error::<T, I>::CallbackFailed);
770770

771771
let metadata = Metadata::<T, I>::take(&id);
772772
T::Currency::unreserve(

frame/assets/src/lib.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,14 @@ const LOG_TARGET: &str = "runtime::assets";
178178
/// Trait with callbacks that are executed after successfull asset creation or destruction.
179179
pub trait AssetsCallback<AssetId, AccountId> {
180180
/// Indicates that asset with `id` was successfully created by the `owner`
181-
fn created(_id: &AssetId, _owner: &AccountId) {}
181+
fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> {
182+
Ok(())
183+
}
182184

183185
/// Indicates that asset with `id` has just been destroyed
184-
fn destroyed(_id: &AssetId) {}
186+
fn destroyed(_id: &AssetId) -> Result<(), ()> {
187+
Ok(())
188+
}
185189
}
186190

187191
/// Empty implementation in case no callbacks are required.
@@ -560,6 +564,8 @@ pub mod pallet {
560564
IncorrectStatus,
561565
/// The asset should be frozen before the given operation.
562566
NotFrozen,
567+
/// Callback action resulted in error
568+
CallbackFailed,
563569
}
564570

565571
#[pallet::call]
@@ -618,13 +624,12 @@ pub mod pallet {
618624
status: AssetStatus::Live,
619625
},
620626
);
621-
627+
ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::<T, I>::CallbackFailed);
622628
Self::deposit_event(Event::Created {
623629
asset_id: id,
624630
creator: owner.clone(),
625631
owner: admin,
626632
});
627-
T::CallbackHandle::created(&id, &owner);
628633

629634
Ok(())
630635
}

frame/assets/src/mock.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,44 @@ impl pallet_balances::Config for Test {
9191

9292
pub struct AssetsCallbackHandle;
9393
impl AssetsCallback<AssetId, AccountId> for AssetsCallbackHandle {
94-
fn created(_id: &AssetId, _owner: &AccountId) {
95-
storage::set(b"asset_created", &().encode());
94+
fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> {
95+
if Self::should_err() {
96+
Err(())
97+
} else {
98+
storage::set(Self::CREATED.as_bytes(), &().encode());
99+
Ok(())
100+
}
96101
}
97102

98-
fn destroyed(_id: &AssetId) {
99-
storage::set(b"asset_destroyed", &().encode());
103+
fn destroyed(_id: &AssetId) -> Result<(), ()> {
104+
if Self::should_err() {
105+
Err(())
106+
} else {
107+
storage::set(Self::DESTROYED.as_bytes(), &().encode());
108+
Ok(())
109+
}
110+
}
111+
}
112+
113+
impl AssetsCallbackHandle {
114+
pub const CREATED: &'static str = "asset_created";
115+
pub const DESTROYED: &'static str = "asset_destroyed";
116+
117+
const RETURN_ERROR: &'static str = "return_error";
118+
119+
// Configures `Self` to return `Ok` when callbacks are invoked
120+
pub fn set_return_ok() {
121+
storage::clear(Self::RETURN_ERROR.as_bytes());
122+
}
123+
124+
// Configures `Self` to return `Err` when callbacks are invoked
125+
pub fn set_return_error() {
126+
storage::set(Self::RETURN_ERROR.as_bytes(), &().encode());
127+
}
128+
129+
// If `true`, callback should return `Err`, `Ok` otherwise.
130+
fn should_err() -> bool {
131+
storage::exists(Self::RETURN_ERROR.as_bytes())
100132
}
101133
}
102134

frame/assets/src/tests.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,28 +1261,58 @@ fn querying_roles_should_work() {
12611261
#[test]
12621262
fn normal_asset_create_and_destroy_callbacks_should_work() {
12631263
new_test_ext().execute_with(|| {
1264-
assert!(storage::get(b"asset_created").is_none());
1265-
assert!(storage::get(b"asset_destroyed").is_none());
1264+
assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none());
1265+
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none());
12661266

12671267
Balances::make_free_balance_be(&1, 100);
12681268
assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1));
1269-
assert!(storage::get(b"asset_created").is_some());
1270-
assert!(storage::get(b"asset_destroyed").is_none());
1269+
assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some());
1270+
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none());
12711271

12721272
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
12731273
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
12741274
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
1275+
// Callback still hasn't been invoked
1276+
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none());
1277+
12751278
assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0));
1276-
assert!(storage::get(b"asset_destroyed").is_some());
1279+
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_some());
12771280
});
12781281
}
12791282

12801283
#[test]
12811284
fn root_asset_create_should_work() {
12821285
new_test_ext().execute_with(|| {
1283-
assert!(storage::get(b"asset_created").is_none());
1286+
assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none());
12841287
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
1285-
assert!(storage::get(b"asset_created").is_some());
1286-
assert!(storage::get(b"asset_destroyed").is_none());
1288+
assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some());
1289+
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none());
1290+
});
1291+
}
1292+
1293+
#[test]
1294+
fn asset_create_and_destroy_is_reverted_if_callback_fails() {
1295+
new_test_ext().execute_with(|| {
1296+
// Asset creation fails due to callback failure
1297+
AssetsCallbackHandle::set_return_error();
1298+
Balances::make_free_balance_be(&1, 100);
1299+
assert_noop!(
1300+
Assets::create(RuntimeOrigin::signed(1), 0, 1, 1),
1301+
Error::<Test>::CallbackFailed
1302+
);
1303+
1304+
// Callback succeeds, so asset creation succeeds
1305+
AssetsCallbackHandle::set_return_ok();
1306+
assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1));
1307+
1308+
// Asset destroy should fail due to callback failure
1309+
AssetsCallbackHandle::set_return_error();
1310+
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
1311+
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
1312+
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
1313+
assert_noop!(
1314+
Assets::finish_destroy(RuntimeOrigin::signed(1), 0),
1315+
Error::<Test>::CallbackFailed
1316+
);
12871317
});
12881318
}

0 commit comments

Comments
 (0)