Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Pallet assets improvements (#13543)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dinonard authored Mar 13, 2023
1 parent 1bbde5d commit 30aa444
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 18 deletions.
4 changes: 2 additions & 2 deletions frame/assets/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,8 +661,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
status: AssetStatus::Live,
},
);
ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::<T, I>::CallbackFailed);
Self::deposit_event(Event::ForceCreated { asset_id: id, owner: owner.clone() });
T::CallbackHandle::created(&id, &owner);
Ok(())
}

Expand Down Expand Up @@ -752,7 +752,6 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
approvals_destroyed: removed_approvals as u32,
approvals_remaining: details.approvals as u32,
});
T::CallbackHandle::destroyed(&id);
Ok(())
})?;
Ok(removed_approvals)
Expand All @@ -767,6 +766,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
ensure!(details.accounts == 0, Error::<T, I>::InUse);
ensure!(details.approvals == 0, Error::<T, I>::InUse);
ensure!(T::CallbackHandle::destroyed(&id).is_ok(), Error::<T, I>::CallbackFailed);

let metadata = Metadata::<T, I>::take(&id);
T::Currency::unreserve(
Expand Down
13 changes: 9 additions & 4 deletions frame/assets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,14 @@ const LOG_TARGET: &str = "runtime::assets";
/// Trait with callbacks that are executed after successfull asset creation or destruction.
pub trait AssetsCallback<AssetId, AccountId> {
/// Indicates that asset with `id` was successfully created by the `owner`
fn created(_id: &AssetId, _owner: &AccountId) {}
fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> {
Ok(())
}

/// Indicates that asset with `id` has just been destroyed
fn destroyed(_id: &AssetId) {}
fn destroyed(_id: &AssetId) -> Result<(), ()> {
Ok(())
}
}

/// Empty implementation in case no callbacks are required.
Expand Down Expand Up @@ -560,6 +564,8 @@ pub mod pallet {
IncorrectStatus,
/// The asset should be frozen before the given operation.
NotFrozen,
/// Callback action resulted in error
CallbackFailed,
}

#[pallet::call]
Expand Down Expand Up @@ -618,13 +624,12 @@ pub mod pallet {
status: AssetStatus::Live,
},
);

ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::<T, I>::CallbackFailed);
Self::deposit_event(Event::Created {
asset_id: id,
creator: owner.clone(),
owner: admin,
});
T::CallbackHandle::created(&id, &owner);

Ok(())
}
Expand Down
40 changes: 36 additions & 4 deletions frame/assets/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,44 @@ impl pallet_balances::Config for Test {

pub struct AssetsCallbackHandle;
impl AssetsCallback<AssetId, AccountId> for AssetsCallbackHandle {
fn created(_id: &AssetId, _owner: &AccountId) {
storage::set(b"asset_created", &().encode());
fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> {
if Self::should_err() {
Err(())
} else {
storage::set(Self::CREATED.as_bytes(), &().encode());
Ok(())
}
}

fn destroyed(_id: &AssetId) {
storage::set(b"asset_destroyed", &().encode());
fn destroyed(_id: &AssetId) -> Result<(), ()> {
if Self::should_err() {
Err(())
} else {
storage::set(Self::DESTROYED.as_bytes(), &().encode());
Ok(())
}
}
}

impl AssetsCallbackHandle {
pub const CREATED: &'static str = "asset_created";
pub const DESTROYED: &'static str = "asset_destroyed";

const RETURN_ERROR: &'static str = "return_error";

// Configures `Self` to return `Ok` when callbacks are invoked
pub fn set_return_ok() {
storage::clear(Self::RETURN_ERROR.as_bytes());
}

// Configures `Self` to return `Err` when callbacks are invoked
pub fn set_return_error() {
storage::set(Self::RETURN_ERROR.as_bytes(), &().encode());
}

// If `true`, callback should return `Err`, `Ok` otherwise.
fn should_err() -> bool {
storage::exists(Self::RETURN_ERROR.as_bytes())
}
}

Expand Down
46 changes: 38 additions & 8 deletions frame/assets/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1261,28 +1261,58 @@ fn querying_roles_should_work() {
#[test]
fn normal_asset_create_and_destroy_callbacks_should_work() {
new_test_ext().execute_with(|| {
assert!(storage::get(b"asset_created").is_none());
assert!(storage::get(b"asset_destroyed").is_none());
assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none());
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none());

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

assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
// Callback still hasn't been invoked
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none());

assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0));
assert!(storage::get(b"asset_destroyed").is_some());
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_some());
});
}

#[test]
fn root_asset_create_should_work() {
new_test_ext().execute_with(|| {
assert!(storage::get(b"asset_created").is_none());
assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none());
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert!(storage::get(b"asset_created").is_some());
assert!(storage::get(b"asset_destroyed").is_none());
assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some());
assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none());
});
}

#[test]
fn asset_create_and_destroy_is_reverted_if_callback_fails() {
new_test_ext().execute_with(|| {
// Asset creation fails due to callback failure
AssetsCallbackHandle::set_return_error();
Balances::make_free_balance_be(&1, 100);
assert_noop!(
Assets::create(RuntimeOrigin::signed(1), 0, 1, 1),
Error::<Test>::CallbackFailed
);

// Callback succeeds, so asset creation succeeds
AssetsCallbackHandle::set_return_ok();
assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1));

// Asset destroy should fail due to callback failure
AssetsCallbackHandle::set_return_error();
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
assert_noop!(
Assets::finish_destroy(RuntimeOrigin::signed(1), 0),
Error::<Test>::CallbackFailed
);
});
}

0 comments on commit 30aa444

Please sign in to comment.