Skip to content

Commit

Permalink
Add unlink_ticker_from_asset_id extrinsic; Add unit tests form lin/un…
Browse files Browse the repository at this point in the history
…link extrinsics; Remove duplicate code (#1728)
  • Loading branch information
HenriqueNogara authored Oct 9, 2024
1 parent 9671d51 commit 4ba8aad
Show file tree
Hide file tree
Showing 9 changed files with 524 additions and 13 deletions.
13 changes: 13 additions & 0 deletions pallets/asset/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,4 +752,17 @@ benchmarks! {
let asset_id = create_sample_asset::<T>(&alice, true);
let ticker = reg_unique_ticker::<T>(alice.origin().into(), None);
}: _(alice.origin, ticker, asset_id)

unlink_ticker_from_asset_id {
set_ticker_registration_config::<T>();
let alice = UserBuilder::<T>::default().generate_did().build("Alice");
let asset_id = create_sample_asset::<T>(&alice, true);
let ticker = reg_unique_ticker::<T>(alice.origin().into(), None);
Module::<T>::link_ticker_to_asset_id(
alice.clone().origin().into(),
ticker,
asset_id
)
.unwrap();
}: _(alice.origin, ticker, asset_id)
}
6 changes: 5 additions & 1 deletion pallets/asset/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ decl_error! {
/// An unexpected error when generating a new asset ID.
AssetIDGenerationError,
/// The ticker doesn't belong to the caller.
TickerNotRegisteredToCaller
TickerNotRegisteredToCaller,
/// The given asset is already linked to a ticker.
AssetIsAlreadyLinkedToATicker,
/// The given ticker is not linked to the given asset.
TickerIsNotLinkedToTheAsset
}
}
62 changes: 51 additions & 11 deletions pallets/asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,20 @@ decl_module! {
pub fn link_ticker_to_asset_id(origin, ticker: Ticker, asset_id: AssetID) {
Self::base_link_ticker_to_asset_id(origin, ticker, asset_id)?;
}

/// Removes the link between a ticker and an asset.
///
/// # Arguments
/// * `origin`: the secondary key of the sender.
/// * `ticker`: the [`Ticker`] that will be unlinked from the given `asset_id`.
/// * `asset_id`: the [`AssetID`] that will be unlink from `ticker`.
///
/// # Permissions
/// * Asset
#[weight = <T as Config>::WeightInfo::unlink_ticker_from_asset_id()]
pub fn unlink_ticker_from_asset_id(origin, ticker: Ticker, asset_id: AssetID) {
Self::base_unlink_ticker_from_asset_id(origin, ticker, asset_id)?;
}
}
}

Expand Down Expand Up @@ -959,7 +973,7 @@ impl<T: Config> Module<T> {
) -> DispatchResult {
let caller_did = <ExternalAgents<T>>::ensure_perms(origin, asset_id)?;

Self::ensure_security_token_exists(&asset_id)?;
Self::ensure_asset_exists(&asset_id)?;

if freeze {
ensure!(Frozen::get(&asset_id) == false, Error::<T>::AlreadyFrozen);
Expand All @@ -981,7 +995,7 @@ impl<T: Config> Module<T> {
asset_name: AssetName,
) -> DispatchResult {
Self::ensure_valid_asset_name(&asset_name)?;
Self::ensure_security_token_exists(&asset_id)?;
Self::ensure_asset_exists(&asset_id)?;

let caller_did = <ExternalAgents<T>>::ensure_perms(origin, asset_id)?;

Expand Down Expand Up @@ -1608,21 +1622,53 @@ impl<T: Config> Module<T> {
ticker_registration.expiry = None;
Ok(())
}
None => return Err(Error::<T>::TickerRegistrationNotFound.into()),
None => Err(Error::<T>::TickerRegistrationNotFound.into()),
}
},
)?;
// The ticker can't be linked to any other asset
Self::ensure_ticker_not_linked(&ticker)?;
// The asset can't be linked to any other ticker
ensure!(
!TickerAssetID::contains_key(ticker),
Error::<T>::TickerIsAlreadyLinkedToAnAsset
!AssetIDTicker::contains_key(asset_id),
Error::<T>::AssetIsAlreadyLinkedToATicker
);
// Links the ticker to the asset
TickerAssetID::insert(ticker, asset_id);
AssetIDTicker::insert(asset_id, ticker);
Self::deposit_event(RawEvent::TickerLinkedToAsset(caller_did, ticker, asset_id));
Ok(())
}

pub fn base_unlink_ticker_from_asset_id(
origin: T::RuntimeOrigin,
ticker: Ticker,
asset_id: AssetID,
) -> DispatchResult {
// Verifies if the caller has the correct permissions for this asset
let caller_did = ExternalAgents::<T>::ensure_perms(origin, asset_id)?;

// The caller must own the ticker
let ticker_registration = UniqueTickerRegistration::<T>::take(ticker)
.ok_or(Error::<T>::TickerRegistrationNotFound)?;
ensure!(
ticker_registration.owner == caller_did,
Error::<T>::TickerNotRegisteredToCaller
);

// The ticker must be linked to the given asset
ensure!(
TickerAssetID::get(ticker) == Some(asset_id),
Error::<T>::TickerIsNotLinkedToTheAsset
);

// Removes the storage links
TickersOwnedByUser::remove(caller_did, ticker);
TickerAssetID::remove(ticker);
AssetIDTicker::remove(asset_id);

Ok(())
}
}

//==========================================================================
Expand Down Expand Up @@ -1859,12 +1905,6 @@ impl<T: Config> Module<T> {
})
}

/// Returns `Ok` if there is a [`AssetDetails`] associated to `asset_id`. Otherwise, returns [`Error::NoSuchAsset`].
fn ensure_security_token_exists(asset_id: &AssetID) -> DispatchResult {
ensure!(Assets::contains_key(asset_id), Error::<T>::NoSuchAsset);
Ok(())
}

/// Ensure asset metadata `value` is within the global limit.
fn ensure_asset_metadata_value_limited(value: &AssetMetadataValue) -> DispatchResult {
ensure!(
Expand Down
1 change: 1 addition & 0 deletions pallets/common/src/traits/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ pub trait WeightInfo {
fn add_mandatory_mediators(n: u32) -> Weight;
fn remove_mandatory_mediators(n: u32) -> Weight;
fn link_ticker_to_asset_id() -> Weight;
fn unlink_ticker_from_asset_id() -> Weight;
}

pub trait AssetFnTrait<Account, Origin> {
Expand Down
238 changes: 238 additions & 0 deletions pallets/runtime/tests/src/asset_pallet/link_ticker_to_asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
use frame_support::StorageMap;
use frame_support::{assert_noop, assert_ok};
use sp_keyring::AccountKeyring;

use pallet_asset::{AssetIDTicker, TickerAssetID};
use polymesh_primitives::Ticker;

use crate::asset_test::{now, set_timestamp};
use crate::storage::User;
use crate::{ExtBuilder, TestStorage};

type Asset = pallet_asset::Module<TestStorage>;
type AssetError = pallet_asset::Error<TestStorage>;
type ExternalAgentsError = pallet_external_agents::Error<TestStorage>;

#[test]
fn link_ticker_to_asset_id_successfully() {
ExtBuilder::default().build().execute_with(|| {
let alice = User::new(AccountKeyring::Alice);
let asset_id = Asset::generate_asset_id(alice.acc(), false);
let ticker: Ticker = Ticker::from_slice_truncated(b"TICKER");

assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker,));

assert_ok!(Asset::create_asset(
alice.origin(),
b"MyAsset".into(),
true,
Default::default(),
Vec::new(),
None,
));

assert_ok!(Asset::link_ticker_to_asset_id(
alice.origin(),
ticker,
asset_id
));

assert_eq!(TickerAssetID::get(ticker), Some(asset_id));
assert_eq!(AssetIDTicker::get(asset_id), Some(ticker));
});
}

#[test]
fn link_ticker_to_asset_id_ticker_not_registered_to_caller() {
ExtBuilder::default().build().execute_with(|| {
let dave = User::new(AccountKeyring::Dave);
let alice = User::new(AccountKeyring::Alice);
let asset_id = Asset::generate_asset_id(dave.acc(), false);
let ticker: Ticker = Ticker::from_slice_truncated(b"TICKER");

assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker,));

assert_ok!(Asset::create_asset(
dave.origin(),
b"MyAsset".into(),
true,
Default::default(),
Vec::new(),
None,
));

assert_noop!(
Asset::link_ticker_to_asset_id(dave.origin(), ticker, asset_id),
AssetError::TickerNotRegisteredToCaller
);
});
}

#[test]
fn link_ticker_to_asset_id_ticker_unauthorized_agent() {
ExtBuilder::default().build().execute_with(|| {
let dave = User::new(AccountKeyring::Dave);
let alice = User::new(AccountKeyring::Alice);
let asset_id = Asset::generate_asset_id(dave.acc(), false);
let ticker: Ticker = Ticker::from_slice_truncated(b"TICKER");

assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker,));

assert_ok!(Asset::create_asset(
dave.origin(),
b"MyAsset".into(),
true,
Default::default(),
Vec::new(),
None,
));

assert_noop!(
Asset::link_ticker_to_asset_id(alice.origin(), ticker, asset_id),
ExternalAgentsError::UnauthorizedAgent
);
});
}

#[test]
fn link_ticker_to_asset_id_expired_ticker() {
ExtBuilder::default().build().execute_with(|| {
let alice = User::new(AccountKeyring::Alice);
let asset_id = Asset::generate_asset_id(alice.acc(), false);
let ticker: Ticker = Ticker::from_slice_truncated(b"TICKER");

assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker,));

assert_ok!(Asset::create_asset(
alice.origin(),
b"MyAsset".into(),
true,
Default::default(),
Vec::new(),
None,
));

set_timestamp(now() + 10001);
assert_noop!(
Asset::link_ticker_to_asset_id(alice.origin(), ticker, asset_id),
AssetError::TickerRegistrationExpired
);
});
}

#[test]
fn link_ticker_to_asset_id_ticker_already_linked() {
ExtBuilder::default().build().execute_with(|| {
let alice = User::new(AccountKeyring::Alice);
let ticker: Ticker = Ticker::from_slice_truncated(b"TICKER");

assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker,));

let asset_id = Asset::generate_asset_id(alice.acc(), false);
assert_ok!(Asset::create_asset(
alice.origin(),
b"MyAsset".into(),
true,
Default::default(),
Vec::new(),
None,
));

assert_ok!(Asset::link_ticker_to_asset_id(
alice.origin(),
ticker,
asset_id
));

let asset_id = Asset::generate_asset_id(alice.acc(), false);
assert_ok!(Asset::create_asset(
alice.origin(),
b"MyAsset".into(),
true,
Default::default(),
Vec::new(),
None,
));

assert_noop!(
Asset::link_ticker_to_asset_id(alice.origin(), ticker, asset_id),
AssetError::TickerIsAlreadyLinkedToAnAsset
);
});
}

#[test]
fn link_ticker_to_asset_asset_already_linked() {
ExtBuilder::default().build().execute_with(|| {
let alice = User::new(AccountKeyring::Alice);
let asset_id = Asset::generate_asset_id(alice.acc(), false);
let ticker: Ticker = Ticker::from_slice_truncated(b"TICKER");
let ticker_1: Ticker = Ticker::from_slice_truncated(b"TICKER1");

assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker,));
assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker_1,));

assert_ok!(Asset::create_asset(
alice.origin(),
b"MyAsset".into(),
true,
Default::default(),
Vec::new(),
None,
));

assert_ok!(Asset::link_ticker_to_asset_id(
alice.origin(),
ticker,
asset_id
));

assert_noop!(
Asset::link_ticker_to_asset_id(alice.origin(), ticker_1, asset_id),
AssetError::AssetIsAlreadyLinkedToATicker
);
});
}

#[test]
fn link_ticker_to_asset_id_after_unlink() {
ExtBuilder::default().build().execute_with(|| {
let alice = User::new(AccountKeyring::Alice);
let asset_id = Asset::generate_asset_id(alice.acc(), false);
let ticker: Ticker = Ticker::from_slice_truncated(b"TICKER");
let ticker_1: Ticker = Ticker::from_slice_truncated(b"TICKER1");

assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker,));
assert_ok!(Asset::register_unique_ticker(alice.origin(), ticker_1,));

assert_ok!(Asset::create_asset(
alice.origin(),
b"MyAsset".into(),
true,
Default::default(),
Vec::new(),
None,
));

assert_ok!(Asset::link_ticker_to_asset_id(
alice.origin(),
ticker,
asset_id
));

assert_ok!(Asset::unlink_ticker_from_asset_id(
alice.origin(),
ticker,
asset_id
));

assert_ok!(Asset::link_ticker_to_asset_id(
alice.origin(),
ticker_1,
asset_id
));

assert_eq!(TickerAssetID::get(ticker_1), Some(asset_id));
assert_eq!(AssetIDTicker::get(asset_id), Some(ticker_1));
});
}
2 changes: 2 additions & 0 deletions pallets/runtime/tests/src/asset_pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ mod accept_ticker_transfer;
mod base_transfer;
mod controller_transfer;
mod issue;
mod link_ticker_to_asset;
mod register_metadata;
mod register_ticker;
mod unlink_ticker_from_asset;

pub(crate) mod setup;
Loading

0 comments on commit 4ba8aad

Please sign in to comment.