Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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